{"record":{"id":"6bb287090d70050e","repo":"Hmbown/CodeWhale","slug":"child-stdout-was-not-valid-utf-8-err","errorCode":null,"errorMessage":"child stdout was not valid UTF-8: {err}","messagePattern":"child stdout was not valid UTF-8: (.+?)","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/mcp/src/stdio_client.rs","lineNumber":264,"sourceCode":"            break;\n        }\n\n        if line.len().saturating_add(available.len()) > max_bytes {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidData,\n                format!(\"JSON-RPC line exceeded the {max_bytes}-byte limit\"),\n            ));\n        }\n        line.extend_from_slice(available);\n        let consumed = available.len();\n        reader.consume(consumed);\n    }\n\n    if line.last() == Some(&b'\\r') {\n        line.pop();\n    }\n    String::from_utf8(line).map(Some).map_err(|err| {\n        io::Error::new(\n            io::ErrorKind::InvalidData,\n            format!(\"child stdout was not valid UTF-8: {err}\"),\n        )\n    })\n}\n\nenum ChildStdoutMessage {\n    Line(String),\n    Invalid(String),\n}\n\nfn response_to_server_request(message: &Value) -> Option<Value> {\n    let method = message.get(\"method\").and_then(Value::as_str)?;\n    let id = message.get(\"id\")?;\n    Some(match method {\n        \"ping\" => json!({\"jsonrpc\": \"2.0\", \"id\": id, \"result\": {}}),\n        _ => json!({\n            \"jsonrpc\": \"2.0\",","sourceCodeStart":246,"sourceCodeEnd":282,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/mcp/src/stdio_client.rs#L246-L282","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nprint(data_bytes.decode('latin-1'))\n// after\nsys.stdout.buffer.write(json.dumps(msg).encode('utf-8')); sys.stdout.write('\\n')","handlingStrategy":"validation","validationCode":"# force UTF-8 stdio in the server's environment\nenv:\n  PYTHONIOENCODING: utf-8\n  LANG: C.UTF-8","typeGuard":null,"tryCatchPattern":"match read_bounded_line(&mut reader, max_bytes) {\n    Err(e) if e.kind() == io::ErrorKind::InvalidData && e.to_string().contains(\"UTF-8\") =>\n        Err(anyhow!(\"MCP server emitted non-UTF-8 on stdout; fix server encoding\")),\n    r => r,\n}","preventionTips":["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)"],"tags":["mcp","stdio","encoding","utf-8"],"backgroundTag":"invalid-json-response","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}