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

  1. Configure the server to log to stderr instead of stdout; fix any stray print statements.
  2. Inspect the embedded `{error}` in the message to see the exact malformed line.
  3. Remove wrapper scripts that print banners to stdout.
  4. 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

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


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)