nikivdev/code · error · anyhow::Error
failed to read from codex app-server: {}
Error message
failed to read from codex app-server: {} What it means
codex_read_response reads the codex app-server's stdout line by line via std::io::Lines. When the underlying BufReader hits an I/O error, the Some(Err(err)) arm surfaces it wrapped in this message. It means the pipe to the child process failed at the OS/read level, not that the child exited.
Source
Thrown at src/skills.rs:1122
let mut line = serde_json::to_string(msg)?;
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
- Check whether the codex app-server process was killed (OOM killer, signal) around the time of the error
- Retry the operation; transient pipe errors often resolve on restart
- Verify system resource limits (ulimit -n) and memory headroom
- Inspect stderr of the child for why the pipe failed
Example fix
// before
bail!("failed to read from codex app-server: {}", err)
// after: classify the error for better diagnosis
bail!("failed to read from codex app-server (kind={:?}): {}", err.kind(), err) Defensive patterns
Strategy: retry
Validate before calling
// ensure the child process is alive before reading
if let Ok(Some(status)) = child.try_wait() {
eprintln!("codex app-server already exited: {}", status);
} Try / catch
match codex_read_response(&mut lines, expected_id, deadline) {
Ok(msg) => handle(msg),
Err(e) if e.to_string().contains("failed to read from codex app-server") => {
log::warn!("io error talking to app-server: {e:#}; restarting");
restart_and_retry()?;
}
Err(e) => return Err(e),
} Prevention
- Monitor for OOM killer / signal kills of the child process
- Keep file descriptor limits healthy (ulimit -n)
- Avoid running in sandboxes that sever parent-child pipes
- Restart the app-server on transient read failures with capped retries
When it happens
Trigger: The ChildStdout reader returns an io::Error during lines.next() while reload_codex_skills_for_cwd awaits a response — e.g. the pipe broke, a descriptor error occurred, or the OS returned EIO.
Common situations: Child process killed abruptly mid-response; fd limits or resource exhaustion breaking the pipe; running in a container/sandbox that severs the pipe; disk or memory pressure causing read failures.
Related errors
- failed to read from codex: {}
- codex app-server response timed out
- codex app-server closed stdout unexpectedly
- 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/be96d087e3be6451.
Report an issue: GitHub.