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

  1. Check whether the codex app-server process was killed (OOM killer, signal) around the time of the error
  2. Retry the operation; transient pipe errors often resolve on restart
  3. Verify system resource limits (ulimit -n) and memory headroom
  4. 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

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


AI-assisted analysis of nikivdev/code@a747e741ae (2026-09-01). Data as JSON: /api/errors/be96d087e3be6451. Report an issue: GitHub.