{"record":{"id":"913b134f8219be34","repo":"ultraworkers/claw-code","slug":"mcp-stdio-stream-closed-while-reading-line","errorCode":null,"errorMessage":"MCP stdio stream closed while reading line","messagePattern":"MCP stdio stream closed while reading line","errorType":"exception","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"rust/crates/runtime/src/mcp_stdio.rs","lineNumber":1214,"sourceCode":"    pub async fn write_all(&mut self, bytes: &[u8]) -> io::Result<()> {\n        self.stdin.write_all(bytes).await\n    }\n\n    pub async fn flush(&mut self) -> io::Result<()> {\n        self.stdin.flush().await\n    }\n\n    pub async fn write_line(&mut self, line: &str) -> io::Result<()> {\n        self.write_all(line.as_bytes()).await?;\n        self.write_all(b\"\\n\").await?;\n        self.flush().await\n    }\n\n    pub async fn read_line(&mut self) -> io::Result<String> {\n        let mut line = String::new();\n        let bytes_read = self.stdout.read_line(&mut line).await?;\n        if bytes_read == 0 {\n            return Err(io::Error::new(\n                io::ErrorKind::UnexpectedEof,\n                \"MCP stdio stream closed while reading line\",\n            ));\n        }\n        Ok(line)\n    }\n\n    pub async fn read_available(&mut self) -> io::Result<Vec<u8>> {\n        let mut buffer = vec![0_u8; 4096];\n        let read = self.stdout.read(&mut buffer).await?;\n        buffer.truncate(read);\n        Ok(buffer)\n    }\n\n    pub async fn write_frame(&mut self, payload: &[u8]) -> io::Result<()> {\n        let encoded = encode_frame(payload);\n        self.write_all(&encoded).await?;\n        self.flush().await","sourceCodeStart":1196,"sourceCodeEnd":1232,"githubUrl":"https://github.com/ultraworkers/claw-code/blob/08106b0c3771ef5b4a5aa176acccd460e88b7325/rust/crates/runtime/src/mcp_stdio.rs#L1196-L1232","documentation":"Client-side `McpStdioProcess::read_line` (runtime/src/mcp_stdio.rs:1214): reading a newline-delimited JSON-RPC line from the spawned MCP server's stdout returned 0 bytes — the child closed its stdout, normally because it exited. Unlike the server-side frame reader there is no clean-EOF path here: ANY EOF is `UnexpectedEof`, even after the server finished its work.","triggerScenarios":"The configured MCP server binary crashing on startup (bad args, missing env like an API key, unsupported Node version — npx wrapper exits immediately); the server exiting after an error; calling read_line after the server already shut down.","commonSituations":"`npx -y @some/mcp-server` where the package fails to resolve; server requiring an env var that was not passed in the MCP config `env` map; server printing a fatal error to stderr and exiting while the client is blocked reading stdout.","solutions":["Run the server command manually with the same args/env from the MCP config and watch stderr — it almost always exits with a message.","Fix the config: correct command/args, and add required env entries to the server's env map.","Treat EOF from read_line as 'server exited', not a retryable IO hiccup — surface stderr instead of looping."],"exampleFix":"# before (config: server exits immediately)\n# mcp config: npx -y @modelcontextprotocol/server-everything --bogus-flag\n\n# after (verify manually, then fix args)\nnpx -y @modelcontextprotocol/server-everything --help   # observe the real failure\n# mcp config: args = [\"-y\", \"@modelcontextprotocol/server-everything\"]","handlingStrategy":"fallback","validationCode":"// smoke-test the server command exactly as configured before wiring it in\n// (run it, send nothing, confirm it stays alive and prints framed output on stdout)\nCommand::new(&transport.command).args(&transport.args).envs(&transport.env).spawn()?","typeGuard":null,"tryCatchPattern":"match process.read_line().await {\n    Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => {\n        let _ = process.child.wait().await;   // collect exit status\n        // report \"MCP server <name> exited\" with captured stderr; do NOT retry blindly\n    }\n    other => other,\n}","preventionTips":["Run the server command manually with its config args/env before adding it to MCP config","Put required secrets in the server's env map, not in the command line","Treat stdout EOF as server exit — capture stderr to surface the real crash reason"],"tags":["mcp","stdio","jsonrpc","eof","process-exit"],"backgroundTag":"unexpected-eof","analyzedSha":"08106b0c3771ef5b4a5aa176acccd460e88b7325","analyzedAt":"2026-08-18T00:29:38.590Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}