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

  1. Fix the peer so every protocol line is a JSON-RPC 2.0 object ({"jsonrpc":"2.0",...}) and route logs to stderr.
  2. Check for protocol/version mismatch and upgrade or configure the agent/client to JSON-RPC 2.0.
  3. Attach an error handler on the stream's readable side to capture and diagnose the offending line.
  4. 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

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

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/cb96c2c1b1671bc2. Report an issue: GitHub.