can1357/oh-my-pi · error
Invalid JSON-RPC message
Error message
Invalid JSON-RPC message
What it means
parseMessage validates each newline-delimited line read by ndJsonStream as a JSON-RPC 2.0 message: it must parse to a non-null, non-array object containing 'jsonrpc' exactly equal to "2.0". Any line failing this throws 'Invalid JSON-RPC message', which errors the readable stream (via controller.error in start).
Source
Thrown at packages/utils/src/acp/stream.ts:79
controller.error(error);
} finally {
reader.releaseLock();
}
},
});
return { writable, readable };
}
function parseMessage(line: string): AnyMessage {
const value: unknown = JSON.parse(line);
if (
typeof value !== "object" ||
value === null ||
Array.isArray(value) ||
!("jsonrpc" in value) ||
value.jsonrpc !== "2.0"
) {
throw new Error("Invalid JSON-RPC message");
}
return value as AnyMessage;
}
View on GitHub (pinned to 9690622007)
Solutions
- Fix the peer so every protocol line is a JSON-RPC 2.0 object ({"jsonrpc":"2.0",...}) and route logs to stderr.
- Check for protocol/version mismatch and upgrade or configure the agent/client to JSON-RPC 2.0.
- Attach an error handler on the stream's readable side to capture and diagnose the offending line.
- Strip or filter non-protocol stdout output before feeding the stream, or use a transport that frames messages instead of assuming clean NDJSON.
Example fix
// before
console.log("agent ready"); // lands on stdout, corrupts the stream
// after
console.error("agent ready"); // stderr keeps the protocol channel clean Defensive patterns
Strategy: validation
Validate before calling
function looksLikeJsonRpc(line: string): boolean {
try {
const v = JSON.parse(line);
return typeof v === "object" && v !== null && !Array.isArray(v) && (v as any).jsonrpc === "2.0";
} catch { return false; }
} Type guard
function isJsonRpcMessage(v: unknown): v is { jsonrpc: "2.0" } & Record<string, unknown> {
return typeof v === "object" && v !== null && !Array.isArray(v)
&& "jsonrpc" in v && (v as Record<string, unknown>).jsonrpc === "2.0";
} Try / catch
try {
for await (const msg of stream.readable) handle(msg);
} catch (err) {
if (err instanceof Error && err.message === "Invalid JSON-RPC message") {
console.error("peer sent a non-JSON-RPC-2.0 line; check for stdout logs or version mismatch");
} else throw err;
} Prevention
- Ensure the peer writes ONLY JSON-RPC 2.0 NDJSON on the protocol channel; route all logs to stderr.
- Verify both sides negotiate the same protocol version (jsonrpc "2.0").
- Handle stream.readable errors so one bad line surfaces as a diagnosable failure, not a hang.
- Test agents with a strict NDJSON linter before wiring them into the transport.
When it happens
Trigger: The connected peer writes a JSON line without a jsonrpc field, uses jsonrpc "1.0" or a missing version, emits a bare JSON array, a JSON literal (number/string/bool/null) on its own line, or plain-text/log output (e.g. startup banners, debug prints) interleaved with the newline-delimited protocol stream.
Common situations: Spawning an ACP agent that prints logs to stdout instead of stderr; protocol version mismatch with an older/newer peer; a JSON-RPC 1.0 server; child process emitting progress or banner text on the protocol channel.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Malformed JSON-RPC response
- Malformed JSON-RPC error response
- Invalid ${label}
- Replacement text is not valid UTF-8: {err}
- invalid glob `{pattern}`: {error}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/cb96c2c1b1671bc2.
Report an issue: GitHub.