Hmbown/CodeWhale · error · anyhow::Error

MCP server '{server}': process closed stdout before answerin

Error message

MCP server '{server}': process closed stdout before answering {method}{}

What it means

The reader thread that drains the MCP server's stdout hit EOF, disconnecting the response channel while a request was outstanding: the child process closed stdout (died) before answering. exit_note() appends the reaped exit status where available. On Linux this is the usual symptom of a server that dies mid-request; on macOS the EPIPE/stdin variant tends to win the race — both describe the same death.

Source

Thrown at crates/mcp/src/stdio_client.rs:361

                );
            }
            return Err(err)
                .with_context(|| format!("MCP server '{server}': failed to send {method}"));
        }

        let deadline = Instant::now() + timeout;
        loop {
            let remaining = deadline.saturating_duration_since(Instant::now());
            if remaining.is_zero() {
                bail!("MCP server '{server}': {method} timed out after {timeout:?}");
            }
            let line = match self.responses.recv_timeout(remaining) {
                Ok(line) => line,
                Err(RecvTimeoutError::Timeout) => {
                    bail!("MCP server '{server}': {method} timed out after {timeout:?}");
                }
                Err(RecvTimeoutError::Disconnected) => {
                    bail!(
                        "MCP server '{server}': process closed stdout before answering {method}{}",
                        self.exit_note()
                    );
                }
            };

            // Servers occasionally emit banners or log lines on stdout, and
            // notifications carry no id. Both are skipped; only the matching
            // response ends the wait.
            let Ok(message) = serde_json::from_str::<Value>(&line) else {
                continue;
            };
            if message.get("id").and_then(Value::as_u64) != Some(id) {
                continue;
            }
            if let Some(error) = message.get("error") {
                bail!("MCP server '{server}': {method} failed: {error}");
            }

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Read the exit status appended to the message, then the server's stderr (inherited) for its crash reason
  2. Fix the server-side crash: capture its panic/exit cause by running the same command and request manually
  3. If the server is killed for memory, raise the limit or make the tool stream/paginate instead of buffering
  4. Restart the server (re-register via register_server) and retry idempotent requests; never blindly retry a tools/call that may have had side effects
Defensive patterns

Strategy: try-catch

Validate before calling

null

Try / catch

match client_call(&client, tool, args) {
    Ok(v) => Ok(v),
    Err(err) if err.to_string().contains("closed stdout") => {
        // server died mid-request: do NOT auto-retry non-idempotent tools
        Err(err).context("MCP server died mid-request; check its stderr/exit status before retrying"))
    }
    Err(err) => Err(err),
}

Prevention

When it happens

Trigger: Server process crashes while executing a tools/call (panic, OOM kill, segfault); server calls exit() after an internal error; wrapper scripts (npx, docker) exiting and closing stdout; server killed externally (OOM reaper, container stop) during a long request.

Common situations: Memory-hungry MCP servers killed by the OOM killer mid-task; node servers dying on unhandled promise rejections; containers wrapped as MCP servers being stopped; servers with a fixed idle timeout that exit during long operations.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/8fa53522f91b130b. Report an issue: GitHub.