Hmbown/CodeWhale · error
MCP server ' ': invalid child stdout while awaiting
Error message
MCP server '{server}': invalid child stdout while awaiting {method}: {error} What it means
The reader thread parses each line of the child's stdout as JSON; unparseable lines are forwarded as ChildStdoutMessage::Invalid. When request() receives such an invalid-stdout notification while awaiting a response, it fails immediately with the underlying parse error rather than waiting out the timeout.
Solutions
- Configure the server to log to stderr instead of stdout; fix any stray print statements.
- Inspect the embedded `{error}` in the message to see the exact malformed line.
- Remove wrapper scripts that print banners to stdout.
- Verify only newline-delimited JSON-RPC messages are written to the child's stdout.
Example fix
// server before
print("loading model...") // goes to stdout, corrupts protocol
// after
eprintln!("loading model...") // stderr is free-form Defensive patterns
Strategy: validation
Validate before calling
// during development, wrap the server so stdout is protocol-only:
// run `my-server 2>server.log` and check server.log for stray prints
fn stdout_is_clean(log: &str) -> bool {
log.lines().all(|l| serde_json::from_str::<serde_json::Value>(l).is_ok())
} Try / catch
match client.request(method, params, timeout) {
Ok(v) => v,
Err(e) if e.to_string().contains("invalid child stdout") => {
eprintln!("server printed non-JSON to stdout: {e}");
Err(e)
}
Err(e) => Err(e),
} Prevention
- Configure all server logging to stderr; stdout is protocol-only in stdio MCP.
- Grep server code for print/println/console.log writing to stdout.
- Test new servers by capturing stdout and validating each line parses as JSON.
- Keep banner/progress output off stdout in wrapper scripts.
When it happens
Trigger: The MCP server prints non-JSON text (debug logs, banners, panics) to stdout while a request is in flight.
Common situations: Servers using print/println debugging on stdout (stdout is the protocol channel); wrappers or tools emitting progress banners; a panic message interleaved with protocol output.
Understand the failure class
Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.
Related errors
- MCP server ' ': invalid child stdout while awaiting
- child returned a malformed MCP CallToolResult
- child stdout was not valid UTF-8
- connection stdin poisoned by an earlier panic
- failed to read bounded stdio JSON-RPC frame
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/b7f98ab61e77bb48.
Report an issue: GitHub.
Appendix: source
Thrown at crates/mcp/src/stdio_client.rs:893
bail!(
"MCP server '{server}': process closed stdin before answering {method}{}",
self.exit_note()
);
}
return Err(err)
.with_context(|| format!("MCP server '{server}': failed to send {method}"));
}
let deadline = Instant::now() + timeout;
loop {
let remaining = deadline.saturating_duration_since(Instant::now());
if remaining.is_zero() {
bail!("MCP server '{server}': {method} timed out after {timeout:?}");
}
let line = match self.responses.recv_timeout(remaining) {
Ok(ChildStdoutMessage::Line(line)) => line,
Ok(ChildStdoutMessage::Invalid(error)) => {
bail!(
"MCP server '{server}': invalid child stdout while awaiting {method}: {error}"
);
}
Err(RecvTimeoutError::Timeout) => {
bail!("MCP server '{server}': {method} timed out after {timeout:?}");
}
Err(RecvTimeoutError::Disconnected) => {
bail!(
"MCP server '{server}': process closed stdout before answering {method}{}",
self.exit_note()
);
}
};
// Servers occasionally emit banners or log lines on stdout. They
// are skipped; only the matching response ends the wait. Valid
// requests and notifications are handled by the reader before
// they reach this rendezvous.View on GitHub (pinned to 73e0f67d83)