{"record":{"id":"c28eb0b2517de97f","repo":"ultraworkers/claw-code","slug":"mcp-stdio-stream-closed-while-reading-headers","errorCode":null,"errorMessage":"MCP stdio stream closed while reading headers","messagePattern":"MCP stdio stream closed while reading headers","errorType":"exception","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"rust/crates/runtime/src/mcp_server.rs","lineNumber":259,"sourceCode":"        }),\n    }\n}\n\n/// Reads a single LSP-framed JSON-RPC payload from `reader`.\n///\n/// Returns `Ok(None)` on clean EOF before any header bytes have been read,\n/// matching how [`crate::mcp_stdio::McpStdioProcess`] treats stream closure.\nasync fn read_frame(reader: &mut BufReader<Stdin>) -> io::Result<Option<Vec<u8>>> {\n    let mut content_length: Option<usize> = None;\n    let mut first_header = true;\n    loop {\n        let mut line = String::new();\n        let bytes_read = reader.read_line(&mut line).await?;\n        if bytes_read == 0 {\n            if first_header {\n                return Ok(None);\n            }\n            return Err(io::Error::new(\n                io::ErrorKind::UnexpectedEof,\n                \"MCP stdio stream closed while reading headers\",\n            ));\n        }\n        first_header = false;\n        if line == \"\\r\\n\" || line == \"\\n\" {\n            break;\n        }\n        let header = line.trim_end_matches(['\\r', '\\n']);\n        if let Some((name, value)) = header.split_once(':') {\n            if name.trim().eq_ignore_ascii_case(\"Content-Length\") {\n                let parsed = value\n                    .trim()\n                    .parse::<usize>()\n                    .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;\n                content_length = Some(parsed);\n            }\n        }","sourceCodeStart":241,"sourceCodeEnd":277,"githubUrl":"https://github.com/ultraworkers/claw-code/blob/08106b0c3771ef5b4a5aa176acccd460e88b7325/rust/crates/runtime/src/mcp_server.rs#L241-L277","documentation":"Server-side MCP frame reader `read_frame` (runtime/src/mcp_server.rs:259): the header loop got at least one header line (`first_header == false`), then `read_line` returned 0 bytes — stdin closed in the middle of a Content-Length header block, before the blank line that terminates it. Clean EOF before ANY bytes returns `Ok(None)` instead; only mid-header EOF errors with `UnexpectedEof`. This is claw acting as an MCP server over stdio.","triggerScenarios":"MCP client process crashes or is SIGKILLed after writing partial headers; a client that writes a header line then closes stdin without finishing the frame; abrupt pipe teardown during session shutdown.","commonSituations":"IDE/editor restart killing the MCP client mid-write; test harnesses closing the pipe to signal shutdown after emitting a partial frame; OOM-killed clients.","solutions":["Treat this as fatal for the connection: stop the read loop and let the server task shut down (the peer is gone).","On the client side, write frames atomically and flush before exiting so headers are never split by termination.","If you see it on every connect, capture the client's framing code — it is emitting truncated header blocks."],"exampleFix":"// before (client, partial write then exit)\nwrite_all(b\"Content-Length: 42\\r\\n\").await;\nprocess::exit(0);              // server: stream closed while reading headers\n\n// after (client writes the full frame, then closes)\nwrite_all(format!(\"Content-Length: {len}\\r\\n\\r\\n\").as_bytes()).await;\nwrite_all(&body).await;\nflush().await;                   // clean EOF later -> server gets Ok(None)","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"match read_frame(&mut reader).await {\n    Ok(None) => break,                                   // clean EOF before headers: session over\n    Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => break, // client died mid-header: end session, log at debug\n    Err(e) => return Err(e),\n    Ok(Some(frame)) => handle(frame).await?,\n}","preventionTips":["Clients: write each frame with a single write_all + flush, then close stdin only after all frames","Servers: treat mid-header EOF as connection termination, not a corrupt-state condition","Never SIGKILL an MCP client mid-write if graceful shutdown is available"],"tags":["mcp","stdio","jsonrpc","eof"],"backgroundTag":"unexpected-eof","analyzedSha":"08106b0c3771ef5b4a5aa176acccd460e88b7325","analyzedAt":"2026-08-18T00:29:38.590Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}