Hmbown/CodeWhale · error

Stdio transport closed {stderr}

Error message

Stdio transport closed
{stderr}

What it means

The MCP server's stdout hit EOF (the child exited) and the stderr tail ring buffer had content, which is appended so the exit reason is visible. This is the classic 'MCP server failed at startup' error: the process was spawned, then terminated while printing a message to stderr.

Source

Thrown at crates/tui/src/mcp/stdio.rs:325

        let mut line_bytes: Vec<u8> = Vec::new();
        loop {
            // Bounded read: a server emitting a newline-free multi-GB "line"
            // must not OOM us (read_line is unbounded).
            let bytes =
                match read_line_capped(&mut self.reader, &mut line_bytes, MAX_MCP_RESPONSE_BYTES)
                    .await
                {
                    Ok(b) => b,
                    Err(err) => {
                        if let Some(stderr) = format_stderr_context(&self.stderr_tail).await {
                            anyhow::bail!("Stdio transport read error: {err}\n{stderr}");
                        }
                        return Err(err.into());
                    }
                };
            if bytes == 0 {
                if let Some(stderr) = format_stderr_context(&self.stderr_tail).await {
                    anyhow::bail!("Stdio transport closed\n{stderr}");
                }
                anyhow::bail!("Stdio transport closed");
            }

            let line = String::from_utf8_lossy(&line_bytes);
            let trimmed = line.trim();
            if trimmed.is_empty() {
                continue;
            }

            return Ok(trimmed.as_bytes().to_vec());
        }
    }

    /// Send SIGTERM and wait up to `STDIO_SHUTDOWN_GRACE` for graceful exit,
    /// then force termination and reap the child as the backstop.
    async fn shutdown(&mut self) {
        let mut child = self.child.lock().await;

View on GitHub (pinned to 8880682c63)

Solutions

  1. Read the stderr excerpt in the message — it is the server's own failure output
  2. Copy the command and args from the config and run them manually; fix until the server starts and stays up
  3. Ensure the runtime (node, python, uvx, docker) is installed and visible on the app's PATH
  4. Declare any required env vars (API keys) in the server configuration

Example fix

// before: server exits with 'missing required argument: PATH' on stderr
command: npx -y @modelcontextprotocol/server-filesystem

// after: required argument supplied
command: npx -y @modelcontextprotocol/server-filesystem /home/me/projects
Defensive patterns

Strategy: try-catch

Type guard

```rust
fn exited_with_stderr(msg: &str) -> bool {
    msg.starts_with("Stdio transport closed\n")
}
```

Try / catch

```rust
match stdio_transport.recv().await {
    Err(e) if exited_with_stderr(&format!("{e:#}")) => {
        surface_stderr_tail(&e); // startup failure: show the server's reason
        Err(e)
    }
    other => other.map(|f| f)?,
}
```

Prevention

When it happens

Trigger: The configured command exits immediately after spawn: bad or missing flags, a required env var (API key) unset, missing interpreter (node/npx/python not on PATH), or a package version whose CLI changed.

Common situations: Wrong command/args in the MCP server entry; missing required arguments (server prints usage and exits); node not installed for npx-based servers; breaking CLI changes after a package upgrade.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/92ca0b6dbfb9a1a4. Report an issue: GitHub.