{"record":{"id":"6c732e21f2d6cedd","repo":"Hmbown/CodeWhale","slug":"stdio-transport-closed-exit","errorCode":null,"errorMessage":"Stdio transport closed{exit}","messagePattern":"Stdio transport closed(.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/mcp/stdio.rs","lineNumber":356,"sourceCode":"                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                // Let the stderr drain task catch up before snapshotting, and\n                // name the exit status: a reviewed plugin's stderr is never\n                // retained, so the status is the only reason the operator\n                // gets when the child dies before the handshake (#5916).\n                tokio::task::yield_now().await;\n                let exit = self.child.lock().await.try_wait().ok().flatten();\n                let exit = exit.map_or_else(String::new, |status| format!(\" ({status})\"));\n                if let Some(stderr) = format_stderr_context(&self.stderr_tail).await {\n                    anyhow::bail!(\"Stdio transport closed{exit}\\n{stderr}\");\n                }\n                anyhow::bail!(\"Stdio transport closed{exit}\");\n            }\n\n            let line_bytes = std::mem::take(&mut self.pending_line);\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;","sourceCodeStart":338,"sourceCodeEnd":374,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/mcp/stdio.rs#L338-L374","documentation":"Variant of the stdio transport closed error when no stderr context could be captured: the child's stdout hit EOF and it exited, but its stderr tail was empty or unavailable. Only the exit status (if retrievable) is reported.","triggerScenarios":"Same as the stderr-annotated variant — EOF on child stdout with child exited — but format_stderr_context returned None (empty stderr tail).","commonSituations":"Child exits silently (e.g. killed by signal before writing anything); server binary writes nothing to stderr; stderr pipe not captured for that spawn mode.","solutions":["Check the exit status in the message for a signal or nonzero code.","Run the MCP server command manually with stderr visible to see its output.","Verify the configured command/args and environment.","If the child was killed, check ulimits, cgroup limits, or supervisor policies."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"Err(e) if e.to_string().starts_with(\"Stdio transport closed\") && !e.to_string().contains('\\n') => {\n    // no stderr captured: reproduce manually with stderr visible\n    return Err(anyhow!(\"MCP server exited silently: {e}; run the command manually to see stderr\"));\n}","preventionTips":["Always capture child stderr so silent exits are diagnosable.","Run silent-exiting commands manually with stderr attached to find the cause.","Check for signal kills via the exit status shown in the message."],"tags":["mcp","stdio","process-exit","subprocess"],"backgroundTag":"connection-refused","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}