nikivdev/code · error · anyhow::Error
codex app-server closed stdout unexpectedly
Error message
codex app-server closed stdout unexpectedly
What it means
codex_read_response expects the codex app-server to keep its stdout open until it answers. When lines.next() returns None, the stream reached EOF — the child closed stdout (typically because the process exited or crashed) without sending the expected response. The error is thrown so callers see a clear cause instead of an empty reply.
Source
Thrown at src/skills.rs:1123
line.push('\n');
writer.write_all(line.as_bytes())?;
writer.flush()?;
Ok(())
}
fn codex_read_response(
lines: &mut std::io::Lines<std::io::BufReader<std::process::ChildStdout>>,
expected_id: u64,
deadline: Instant,
) -> Result<serde_json::Value> {
loop {
if Instant::now() >= deadline {
bail!("codex app-server response timed out");
}
let line = match lines.next() {
Some(Ok(line)) => line,
Some(Err(err)) => bail!("failed to read from codex app-server: {}", err),
None => bail!("codex app-server closed stdout unexpectedly"),
};
if line.trim().is_empty() {
continue;
}
let msg: serde_json::Value = serde_json::from_str(&line)
.with_context(|| format!("invalid JSON from codex app-server: {}", line))?;
if msg.get("id").and_then(|v| v.as_u64()) == Some(expected_id) {
if let Some(err) = msg.get("error") {
let message = err
.get("message")
.and_then(|v| v.as_str())
.unwrap_or("unknown codex app-server error");
bail!("codex app-server error: {}", message);
}
return Ok(msg);
}
}
}View on GitHub (pinned to a747e741ae)
Solutions
- Run the codex app-server manually and reproduce the request to see its exit output/stderr
- Check the child's exit status after failure to capture why it exited (crash, panic, signal)
- Verify the codex binary version/protocol compatibility
- Ensure the child is spawned with inherited/stderr captured so crash reasons are visible
Example fix
// before
None => bail!("codex app-server closed stdout unexpectedly"),
// after: include exit status captured elsewhere
None => bail!("codex app-server closed stdout unexpectedly (exit status: {:?})", child.try_wait()), Defensive patterns
Strategy: try-catch
Validate before calling
// verify the child is still alive before expecting a response
if let Ok(Some(status)) = child.try_wait() {
bail!("codex app-server already exited with {} before responding", status);
} Try / catch
match codex_read_response(&mut lines, expected_id, deadline) {
Ok(msg) => handle(msg),
Err(e) if e.to_string().contains("closed stdout unexpectedly") => {
eprintln!("codex app-server crashed; stderr was: {stderr}");
restart_app_server()?;
}
Err(e) => return Err(e),
} Prevention
- Always capture the app-server's stderr so crash reasons are visible
- Pin a codex binary version compatible with your request protocol
- Check child exit status immediately after any stream EOF
- Handle known crash triggers (bad cwd, missing workspace) before sending requests
When it happens
Trigger: The codex app-server child exits or its stdout closes before it emits the JSON response matching expected_id during reload_codex_skills_for_cwd; the process crashes on startup; the process is killed by a signal.
Common situations: Codex binary panics on the request; wrong codex executable path causing an immediate exit; version mismatch where the server exits on unknown requests; OOM kill of the child process.
Related errors
- failed to read from codex: {}
- codex app-server response timed out
- failed to read from codex app-server: {}
- codex app-server closed stdout unexpectedly
- codex app-server reader disconnected
AI-assisted analysis of nikivdev/code@a747e741ae (2026-09-01).
Data as JSON: /api/errors/0484181cb4f11286.
Report an issue: GitHub.