{"record":{"id":"3dbe67f9304f390f","repo":"Hmbown/CodeWhale","slug":"stdio-transport-closed","errorCode":null,"errorMessage":"Stdio transport closed","messagePattern":"Stdio transport closed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/mcp/stdio.rs","lineNumber":327,"sourceCode":"            // Bounded read: a server emitting a newline-free multi-GB \"line\"\n            // must not OOM us (read_line is unbounded).\n            let bytes =\n                match read_line_capped(&mut self.reader, &mut line_bytes, MAX_MCP_RESPONSE_BYTES)\n                    .await\n                {\n                    Ok(b) => b,\n                    Err(err) => {\n                        if let Some(stderr) = format_stderr_context(&self.stderr_tail).await {\n                            anyhow::bail!(\"Stdio transport read error: {err}\\n{stderr}\");\n                        }\n                        return Err(err.into());\n                    }\n                };\n            if bytes == 0 {\n                if let Some(stderr) = format_stderr_context(&self.stderr_tail).await {\n                    anyhow::bail!(\"Stdio transport closed\\n{stderr}\");\n                }\n                anyhow::bail!(\"Stdio transport closed\");\n            }\n\n            let line = String::from_utf8_lossy(&line_bytes);\n            let trimmed = line.trim();\n            if trimmed.is_empty() {\n                continue;\n            }\n\n            return Ok(trimmed.as_bytes().to_vec());\n        }\n    }\n\n    /// Send SIGTERM and wait up to `STDIO_SHUTDOWN_GRACE` for graceful exit,\n    /// then force termination and reap the child as the backstop.\n    async fn shutdown(&mut self) {\n        let mut child = self.child.lock().await;\n        terminate_child(&mut child).await;\n    }","sourceCodeStart":309,"sourceCodeEnd":345,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/tui/src/mcp/stdio.rs#L309-L345","documentation":"Stdout EOF with no stderr captured: the server process ended without writing anything to stderr (the ring-buffered tail was empty). Silent exits come from clean status-0 exits (e.g. --help left in args), SIGKILL/OOM kills that never get to print, or wrappers that daemonize and close stdout.","triggerScenarios":"The child exits immediately with code 0 (argument parsing that prints to stdout only), is killed by SIGKILL/OOM before writing stderr, or forks to the background and closes its stdio.","commonSituations":"A --help/--version flag accidentally left in server args; OOM killer silently terminating the child; wrapper scripts that daemonize instead of staying in the foreground.","solutions":["Run the command manually and observe its exit code and output","Check system logs (dmesg, journalctl) for OOM or signal kills of the child","Remove flags like --help/--version from the server args","Confirm the server is a foreground stdio MCP server, not a daemonizing wrapper"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"```rust\nfn is_silent_exit(msg: &str) -> bool {\n    msg.trim() == \"Stdio transport closed\"\n}\n```","tryCatchPattern":"```rust\nmatch stdio_transport.recv().await {\n    Err(e) if is_silent_exit(&format!(\"{e:#}\")) => {\n        check_oom_and_signal_logs(); // silent exits: OOM kill, SIGKILL, or clean exit(0)\n        Err(e)\n    }\n    other => other?,\n}\n```","preventionTips":["Remove --help/--version style flags from server args","Check dmesg/journalctl for OOM kills when the exit is silent","Use foreground stdio servers, not daemonizing wrappers"],"tags":["mcp","stdio","subprocess","exit"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}