{"record":{"id":"d1ff063a9141162f","repo":"Hmbown/CodeWhale","slug":"mcp-server-invalid-child-stdout-while-awaiting","errorCode":null,"errorMessage":"MCP server '{}': invalid child stdout while awaiting {}: {}","messagePattern":"MCP server '(.+?)': invalid child stdout while awaiting (.+?): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/mcp/src/stdio_client.rs","lineNumber":893,"sourceCode":"                bail!(\n                    \"MCP server '{server}': process closed stdin before answering {method}{}\",\n                    self.exit_note()\n                );\n            }\n            return Err(err)\n                .with_context(|| format!(\"MCP server '{server}': failed to send {method}\"));\n        }\n\n        let deadline = Instant::now() + timeout;\n        loop {\n            let remaining = deadline.saturating_duration_since(Instant::now());\n            if remaining.is_zero() {\n                bail!(\"MCP server '{server}': {method} timed out after {timeout:?}\");\n            }\n            let line = match self.responses.recv_timeout(remaining) {\n                Ok(ChildStdoutMessage::Line(line)) => line,\n                Ok(ChildStdoutMessage::Invalid(error)) => {\n                    bail!(\n                        \"MCP server '{server}': invalid child stdout while awaiting {method}: {error}\"\n                    );\n                }\n                Err(RecvTimeoutError::Timeout) => {\n                    bail!(\"MCP server '{server}': {method} timed out after {timeout:?}\");\n                }\n                Err(RecvTimeoutError::Disconnected) => {\n                    bail!(\n                        \"MCP server '{server}': process closed stdout before answering {method}{}\",\n                        self.exit_note()\n                    );\n                }\n            };\n\n            // Servers occasionally emit banners or log lines on stdout. They\n            // are skipped; only the matching response ends the wait. Valid\n            // requests and notifications are handled by the reader before\n            // they reach this rendezvous.","sourceCodeStart":875,"sourceCodeEnd":911,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/crates/mcp/src/stdio_client.rs#L875-L911","documentation":"While awaiting a response, the reader thread parses each child stdout line as JSON-RPC. A line that cannot be parsed is forwarded as `ChildStdoutMessage::Invalid(error)`, and `request` converts that into this bail. The MCP stdio transport requires every stdout line to be a valid JSON-RPC message; anything else (log output, banners, ANSI escapes) poisons the stream. The parse error text is embedded in the message.","triggerScenarios":"`request` is awaiting `{method}` when the reader thread receives a stdout line from the child that fails JSON parsing, e.g. plain text, a partial/truncated JSON line, or non-JSON debug output interleaved with responses.","commonSituations":"A server that prints human-readable logs or ASCII banners to stdout instead of stderr; a server using a different framing (e.g. Content-Length headers, one JSON blob without newline); locale/encoding output corrupting lines; a response so large it is split across writes without a trailing newline yet.","solutions":["Configure the MCP server to send ALL logging/diagnostics to stderr; stdout must carry only JSON-RPC lines.","Check the embedded parse error to see exactly what non-JSON bytes arrived (banners, ANSI codes, or framing mismatch).","Verify the server implements the MCP stdio transport's newline-delimited JSON framing, not another RPC framing.","Update or replace the server binary; many older MCP servers had stdout-pollution bugs fixed in later releases."],"exampleFix":"// before (server code): logging to stdout\nconsole.log(\"server started\");\n// after: stderr for logs, stdout for JSON-RPC only\nconsole.error(\"server started\");","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match conn.request(method, params, timeout).await {\n    Err(e) if e.to_string().contains(\"invalid child stdout\") => {\n        eprintln!(\"server polluted stdout with non-JSON: {e}; route logs to stderr\");\n    }\n    other => other,\n}","preventionTips":["In server code, never print/log to stdout; use stderr exclusively for diagnostics.","Verify the server uses newline-delimited JSON-RPC framing on stdout, not another protocol.","Run a handshake/list smoke test per server version; stdout pollution usually shows up immediately.","Strip or reject ANSI escapes and banners in any wrapper you control."],"tags":["mcp","stdout","json-parse","stdio","framing"],"backgroundTag":"invalid-json-response","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}