fix(coder-lite): make --dev boot the openagents.com server robustly

0d6ebd0be6d6 · AtlantisPleb · · parent 83236d3deb0f

fix(coder-lite): make --dev boot the openagents.com server robustly

The dev server was failing because the runtime tried localhost, which
often resolves to ::1 while Phoenix binds 127.0.0.1. Switched the dev
URL to 127.0.0.1:4000, wait for GET /api/v1/health instead of just a
TCP connect, start the server in its own process group so it survives
the shell's exit, and always set the env for --dev.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By
Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

Not deployed through the forge lane

No push, promotion, build, or deploy receipt references this commit (receipts are scanned over a bounded recent window). Changes shipped by full node replacement carry their proof in the release gate receipt instead.

Changed files

  • modified crates/coder-lite/src/main.rs

Diff

1 file changed, +27 -13

crates/coder-lite/src/main.rs modified +27 -13

@@ -14,14 +14,16 @@

14 14
15 15
use std::env;
16 16
use std::path::PathBuf;
17
use std::process::Stdio;
17 18
use std::time::Duration;
19
use tokio::io::{AsyncReadExt, AsyncWriteExt};
18 20
use tokio::net::TcpStream;
19 21
use tokio::process::Command;
20 22
use tokio::time::{sleep, timeout};
21 23
22 24
use coder_lite::interactive::SessionOptions;
23 25
24
const DEV_BASE_URL: &str = "http://localhost:4000/api/v1";
26
const DEV_BASE_URL: &str = "http://127.0.0.1:4000/api/v1";
25 27
const DEV_API_KEY: &str = "fake";
26 28
27 29
/// Every flag this binary reads. A flag listed here does what it says or the

@@ -32,7 +34,7 @@ coder-lite — the OpenAgents coder, in a terminal.

32 34
Usage: coder-lite [options]
33 35
34 36
Options:
35
  --dev              Talk to a server on this machine at http://localhost:4000.
37
  --dev              Talk to a server on this machine at http://127.0.0.1:4000.
36 38
                     Starts one from ../openagents.com if none is running, and
37 39
                     tolerates one that already is.
38 40
  --lane <name>      Which model answers. `auto` leaves it to the deployment;

@@ -70,12 +72,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {

70 72
                // before the TUI and its tokio tasks start, so no other thread
71 73
                // exists yet.
72 74
                unsafe {
73
                    if env::var("OPENAGENTS_BASE_URL").is_err() {
74
                        env::set_var("OPENAGENTS_BASE_URL", DEV_BASE_URL);
75
                    }
76
                    if env::var("OPENAGENTS_API_KEY").is_err() {
77
                        env::set_var("OPENAGENTS_API_KEY", DEV_API_KEY);
78
                    }
75
                    env::set_var("OPENAGENTS_BASE_URL", DEV_BASE_URL);
76
                    env::set_var("OPENAGENTS_API_KEY", DEV_API_KEY);
79 77
                }
80 78
            }
81 79
            options

@@ -157,10 +155,17 @@ async fn boot_dev_server() -> Result<(), Box<dyn std::error::Error>> {

157 155
    let repo = web_repo()?;
158 156
    eprintln!("starting dev server in {}", repo.display());
159 157
160
    Command::new("sh")
158
    let mut command = Command::new("sh");
159
    command
161 160
        .arg("start_server.sh")
162 161
        .current_dir(&repo)
163
        .spawn()?;
162
        .stdout(Stdio::null())
163
        .stderr(Stdio::null());
164
    // The server outlives this TUI; put it in its own group so the shell's
165
    // exit and any terminal events do not take it down.
166
    #[cfg(unix)]
167
    command.process_group(0);
168
    let _child = command.spawn()?;
164 169
165 170
    for _ in 0..60 {
166 171
        if is_dev_server_up().await {

@@ -173,9 +178,18 @@ async fn boot_dev_server() -> Result<(), Box<dyn std::error::Error>> {

173 178
}
174 179
175 180
async fn is_dev_server_up() -> bool {
176
    timeout(Duration::from_secs(2), TcpStream::connect("127.0.0.1:4000"))
177
        .await
178
        .is_ok()
181
    timeout(Duration::from_secs(2), async {
182
        let mut stream = TcpStream::connect("127.0.0.1:4000").await.ok()?;
183
        let request = "GET /api/v1/health HTTP/1.1\r\nHost: 127.0.0.1:4000\r\nConnection: close\r\n\r\n";
184
        stream.write_all(request.as_bytes()).await.ok()?;
185
        let mut buf = [0u8; 256];
186
        let n = stream.read(&mut buf).await.ok()?;
187
        let head = std::str::from_utf8(&buf[..n]).unwrap_or("");
188
        Some(head.starts_with("HTTP/1.1 200") || head.contains(" 200 "))
189
    })
190
    .await
191
    .unwrap_or(Some(false))
192
    .unwrap_or(false)
179 193
}
180 194
181 195
fn web_repo() -> Result<PathBuf, Box<dyn std::error::Error>> {

This page updates live while a promote is in flight · changelog