nikivdev/code · error · anyhow::Error

codex app-server response timed out

Error message

codex app-server response timed out

What it means

codex_read_response polls the codex app-server child process stdout for a JSON-RPC response with a specific id, but only until a deadline. When the deadline elapses before the expected response arrives, it aborts with this message instead of blocking forever. It is a deliberate timeout guard against a hung or stuck app-server process.

Source

Thrown at src/skills.rs:1118

    source: Option<String>,
}

fn codex_write_msg(writer: &mut dyn Write, msg: &serde_json::Value) -> Result<()> {
    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);

View on GitHub (pinned to a747e741ae)

Solutions

  1. Check that the codex app-server process is alive and responsive (kill hung instances and retry)
  2. Verify the codex binary version matches the protocol this tool expects; upgrade or downgrade codex
  3. Re-run the command after freeing system resources; the deadline is fixed so a transiently slow machine can cause this
  4. Add logging around the request to confirm the request id sent matches the one codex_read_response waits for

Example fix

// before: waiting on a hung server with no diagnosis
bail!("codex app-server response timed out");
// after: surface the elapsed time and hint at the hung process
bail!("codex app-server response timed out after {:?} (is the codex app-server hung?)", deadline.duration_since(Instant::now()));
Defensive patterns

Strategy: retry

Validate before calling

// check the codex app-server is responsive before the call
if let Ok(status) = std::process::Command::new("pgrep").args(["-f", "codex app-server"]).status() {
    if !status.success() { eprintln!("codex app-server not running; expect timeout"); }
}

Try / catch

match reload_codex_skills_for_cwd() {
    Ok(skills) => apply(skills),
    Err(e) if e.to_string().contains("timed out") => {
        // kill wedged server, back off, retry once
        kill_codex_app_server();
        std::thread::sleep(RETRY_DELAY);
        retry_reload()?;
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling reload_codex_skills_for_cwd (which sends a request to the codex app-server via codex_read_response) when the app-server accepts the request but never sends back a response with the expected id before the deadline; a wedged or extremely slow codex process; a request id mismatch so the expected reply never matches.

Common situations: Codex app-server is frozen or overloaded; the codex binary version speaks a different protocol so responses carry unexpected ids; system under heavy load makes the child slow to answer; stdout produces unrelated chatter so the matching id never appears before timeout.

Understand the failure class

Related errors


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