Hmbown/CodeWhale · error · io::Error
child stdout was not valid UTF-8
Error message
child stdout was not valid UTF-8: {err} What it means
After collecting one bounded line from the MCP child's stdout (with any trailing CR stripped), read_bounded_line converts the bytes to String via String::from_utf8; invalid UTF-8 becomes InvalidData with this message. The JSON-RPC-over-stdio transport is defined as UTF-8 text lines, so non-UTF-8 output means the child is not speaking the protocol.
Solutions
- Fix the server to write strictly UTF-8 to stdout (set stdout encoding explicitly; on Windows use UTF-8 mode)
- Ensure logs/progress go to stderr and stdout carries only JSON-RPC text
- Check the child's locale/env (e.g. PYTHONIOENCODING=utf-8, LANG=C.UTF-8)
- Confirm the configured command launches the intended MCP server binary
Example fix
// before
print(data_bytes.decode('latin-1'))
// after
sys.stdout.buffer.write(json.dumps(msg).encode('utf-8')); sys.stdout.write('\n') Defensive patterns
Strategy: validation
Validate before calling
# force UTF-8 stdio in the server's environment env: PYTHONIOENCODING: utf-8 LANG: C.UTF-8
Try / catch
match read_bounded_line(&mut reader, max_bytes) {
Err(e) if e.kind() == io::ErrorKind::InvalidData && e.to_string().contains("UTF-8") =>
Err(anyhow!("MCP server emitted non-UTF-8 on stdout; fix server encoding")),
r => r,
} Prevention
- Configure server language runtimes for UTF-8 stdio explicitly
- Send binary/banners to stderr only
- Test server output encoding on all target platforms (Windows codepages are the usual culprit)
When it happens
Trigger: The MCP server writes binary data, non-UTF-8 encoded text (e.g. Latin-1 log lines), or a partially-flushed multibyte character split across chunk boundaries handled incorrectly by the server, into stdout; caught during spawn_with_timeouts message reading.
Common situations: Windows server writing console codepage bytes instead of UTF-8; server emitting a binary banner or BOM-wrapped/handled output; wrong binary launched (e.g. a compiled artifact printing progress bytes); locale env forcing non-UTF-8 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
- artifact name is not portable UTF-8
- child returned a malformed MCP CallToolResult
- Configuration and skill entrypoints must be UTF-8.
- connection stdin poisoned by an earlier panic
- current Codewhale executable path is not valid UTF-8
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/6bb287090d70050e.
Report an issue: GitHub.
Appendix: source
Thrown at crates/mcp/src/stdio_client.rs:264
break;
}
if line.len().saturating_add(available.len()) > max_bytes {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("JSON-RPC line exceeded the {max_bytes}-byte limit"),
));
}
line.extend_from_slice(available);
let consumed = available.len();
reader.consume(consumed);
}
if line.last() == Some(&b'\r') {
line.pop();
}
String::from_utf8(line).map(Some).map_err(|err| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("child stdout was not valid UTF-8: {err}"),
)
})
}
enum ChildStdoutMessage {
Line(String),
Invalid(String),
}
fn response_to_server_request(message: &Value) -> Option<Value> {
let method = message.get("method").and_then(Value::as_str)?;
let id = message.get("id")?;
Some(match method {
"ping" => json!({"jsonrpc": "2.0", "id": id, "result": {}}),
_ => json!({
"jsonrpc": "2.0",View on GitHub (pinned to 73e0f67d83)