{"record":{"id":"39991cda28936f9a","repo":"Hmbown/CodeWhale","slug":"32603","errorCode":"-32603","errorMessage":"runtime API bridge exited before becoming ready (status {status})","messagePattern":"runtime API bridge exited before becoming ready \\(status (.+?)\\)","errorType":"exception","errorClass":"JsonRpcError","httpStatus":null,"severity":"error","filePath":"crates/app-server/src/lib.rs","lineNumber":1072,"sourceCode":"            .arg(port.to_string())\n            .env(\"CODEWHALE_RUNTIME_TOKEN\", auth_token)\n            .env(\"DEEPSEEK_RUNTIME_TOKEN\", auth_token)\n            .stdin(Stdio::null())\n            .stdout(Stdio::null())\n            .stderr(Stdio::null());\n        if let Some(config_path) = config_path {\n            command.arg(\"--config\").arg(config_path);\n        }\n        Ok(command)\n    }\n\n    async fn wait_until_ready(&mut self) -> Result<()> {\n        let deadline = Instant::now() + Duration::from_secs(15);\n        loop {\n            if let Some(child) = self.child.as_mut()\n                && let Some(status) = child.try_wait()?\n            {\n                return Err(anyhow!(\n                    \"runtime API bridge exited before becoming ready (status {status})\"\n                ));\n            }\n\n            match self\n                .client\n                .get(format!(\"{}/health\", self.base_url))\n                .send()\n                .await\n            {\n                Ok(response) if response.status().is_success() => return Ok(()),\n                _ if Instant::now() >= deadline => {\n                    bail!(\n                        \"timed out waiting for runtime API bridge at {}/health\",\n                        self.base_url\n                    )\n                }\n                _ => tokio::time::sleep(Duration::from_millis(50)).await,","sourceCodeStart":1054,"sourceCodeEnd":1090,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/app-server/src/lib.rs#L1054-L1090","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["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)","Ensure the spawned binary is the same build as the bridge -- avoid relying on the PATH fallback to an old installed codewhale","Validate the config path handed to RuntimeBridge::start exists and parses before spawning","Check for port squatting on 127.0.0.1 and retry startup (the port is picked fresh each spawn)","Drop the cached bridge (the invalidate_stdio_bridge path) so the next stdio message respawns a fresh child"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Before starting the bridge, sanity-check that the child can run:\nuse std::process::Command;\n\nfn runtime_child_launchable() -> bool {\n    let exe = std::env::current_exe()\n        .map(|p| p.to_string_lossy().to_string())\n        .unwrap_or_else(|_| \"codewhale\".into());\n    Command::new(exe)\n        .arg(\"app-server\")\n        .arg(\"--help\")\n        .stdout(std::process::Stdio::null())\n        .stderr(std::process::Stdio::null())\n        .status()\n        .map(|s| s.success())\n        .unwrap_or(false)\n}","typeGuard":null,"tryCatchPattern":"// On -32603 'runtime API bridge exited before becoming ready': drop the cached\n// bridge and let the next request respawn it (fresh port, fresh child).\nmatch bridge.message_thread(...).await {\n    Ok(v) => v,\n    Err(err) if err.to_string().contains(\"exited before becoming ready\") => {\n        invalidate_stdio_bridge(state).await; // next stdio message spawns a new child\n        return Err(JsonRpcError::internal(\n            \"runtime bridge restart scheduled; retry the request\".into(),\n        ));\n    }\n    Err(err) => return Err(JsonRpcError::internal(err.to_string())),\n}","preventionTips":["Guarantee the spawned runtime binary is the same build as the bridge (avoid the PATH 'codewhale' fallback in mixed-version installs)","Validate any --config path exists before passing it to RuntimeBridge::start","The child's stdio is null -- when debugging, always reproduce 'app-server --http ...' by hand to see the real startup error"],"tags":["rust","json-rpc","subprocess","startup","http","app-server"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}