Hmbown/CodeWhale · error · JsonRpcError

-32603

-32603

Error message

runtime API bridge exited before becoming ready (status {status})

What it means

The stdio app-server bridge (RuntimeBridge::start) spawns a child runtime process (the current executable, or 'codewhale' from PATH, run as 'app-server --http --host 127.0.0.1 --port <p>' with CODEWHALE_RUNTIME_TOKEN/DEEPSEEK_RUNTIME_TOKEN in its environment) and polls GET /health for up to 15 seconds. wait_until_ready() observed via try_wait() that the child had already terminated before /health ever succeeded, so the bridge is unusable; the child's exit status is embedded. It surfaces to JSON-RPC clients as internal error -32603. Note the child is spawned with stdin/stdout/stderr set to null, so its failure reason is invisible unless you run it by hand.

Source

Thrown at crates/app-server/src/lib.rs:1072

            .arg(port.to_string())
            .env("CODEWHALE_RUNTIME_TOKEN", auth_token)
            .env("DEEPSEEK_RUNTIME_TOKEN", auth_token)
            .stdin(Stdio::null())
            .stdout(Stdio::null())
            .stderr(Stdio::null());
        if let Some(config_path) = config_path {
            command.arg("--config").arg(config_path);
        }
        Ok(command)
    }

    async fn wait_until_ready(&mut self) -> Result<()> {
        let deadline = Instant::now() + Duration::from_secs(15);
        loop {
            if let Some(child) = self.child.as_mut()
                && let Some(status) = child.try_wait()?
            {
                return Err(anyhow!(
                    "runtime API bridge exited before becoming ready (status {status})"
                ));
            }

            match self
                .client
                .get(format!("{}/health", self.base_url))
                .send()
                .await
            {
                Ok(response) if response.status().is_success() => return Ok(()),
                _ if Instant::now() >= deadline => {
                    bail!(
                        "timed out waiting for runtime API bridge at {}/health",
                        self.base_url
                    )
                }
                _ => tokio::time::sleep(Duration::from_millis(50)).await,

View on GitHub (pinned to 8880682c63)

Solutions

  1. Reproduce the child manually to see its real error: <exe> app-server --http --host 127.0.0.1 --port 18080 with CODEWHALE_RUNTIME_TOKEN=cwrt_test (its output is discarded by the bridge)
  2. Ensure the spawned binary is the same build as the bridge -- avoid relying on the PATH fallback to an old installed codewhale
  3. Validate the config path handed to RuntimeBridge::start exists and parses before spawning
  4. Check for port squatting on 127.0.0.1 and retry startup (the port is picked fresh each spawn)
  5. Drop the cached bridge (the invalidate_stdio_bridge path) so the next stdio message respawns a fresh child
Defensive patterns

Strategy: retry

Validate before calling

// Before starting the bridge, sanity-check that the child can run:
use std::process::Command;

fn runtime_child_launchable() -> bool {
    let exe = std::env::current_exe()
        .map(|p| p.to_string_lossy().to_string())
        .unwrap_or_else(|_| "codewhale".into());
    Command::new(exe)
        .arg("app-server")
        .arg("--help")
        .stdout(std::process::Stdio::null())
        .stderr(std::process::Stdio::null())
        .status()
        .map(|s| s.success())
        .unwrap_or(false)
}

Try / catch

// On -32603 'runtime API bridge exited before becoming ready': drop the cached
// bridge and let the next request respawn it (fresh port, fresh child).
match bridge.message_thread(...).await {
    Ok(v) => v,
    Err(err) if err.to_string().contains("exited before becoming ready") => {
        invalidate_stdio_bridge(state).await; // next stdio message spawns a new child
        return Err(JsonRpcError::internal(
            "runtime bridge restart scheduled; retry the request".into(),
        ));
    }
    Err(err) => return Err(JsonRpcError::internal(err.to_string())),
}

Prevention

When it happens

Trigger: std::env::current_exe() is unavailable and PATH resolves to an older installed 'codewhale' binary that lacks the 'app-server --http' interface or rejects the runtime-token env vars; the reserved port (bound then released by reserve_runtime_port before spawn) is retaken by another process; the optional --config path passed to the child points to a missing or invalid file; the child panics at startup (bad config, incompatible build).

Common situations: Version skew between a library build and an older codewhale binary on PATH; a stale or moved config file; tight CI/sandbox environments where the spawn or listen fails immediately.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/39991cda28936f9a. Report an issue: GitHub.