can1357/oh-my-pi · error

rpc frame must be an object

Error message

rpc frame must be an object

What it means

RpcFrameDecoder.push expects every input line (after JSON.parse) to be a record (plain object). This error is thrown when a parsed JSONL line decodes to a non-object JSON value — a string, number, boolean, null, or an array — that is also not an rpc_chunk frame.

Source

Thrown at packages/coding-agent/src/modes/rpc/rpc-frame.ts:142

	if (
		typeof data !== "string" ||
		data.length === 0 ||
		!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(data)
	)
		throw new Error("invalid rpc chunk data");
	const bytes = Buffer.from(data, "base64");
	if (bytes.toString("base64") !== data) throw new Error("invalid rpc chunk data");
	return bytes;
}

/** Reassemble protocol v2 chunk frames after each JSONL line has been parsed. */
export class RpcFrameDecoder {
	#pending?: PendingRpcChunks;

	push(value: unknown): object | undefined {
		if (!isRpcChunkFrame(value)) {
			if (this.#pending) throw new Error("rpc chunk sequence interrupted");
			if (!isRecord(value)) throw new Error("rpc frame must be an object");
			return value;
		}
		const { chunkId, index, count, byteLength } = value;
		if (
			typeof chunkId !== "string" ||
			chunkId.length === 0 ||
			chunkId.length > 128 ||
			!Number.isSafeInteger(index) ||
			!Number.isSafeInteger(count) ||
			!Number.isSafeInteger(byteLength) ||
			index < 0 ||
			count < 2 ||
			count > Math.ceil(MAX_RPC_REASSEMBLED_BYTES / RPC_CHUNK_PAYLOAD_BYTES) ||
			index >= count ||
			byteLength < MAX_RPC_FRAME_BYTES ||
			byteLength > MAX_RPC_REASSEMBLED_BYTES
		)
			throw new Error("invalid rpc chunk metadata");

View on GitHub (pinned to 9690622007)

Solutions

  1. Always wrap payloads in a frame object with a `type` field before JSON.stringify.
  2. Check that only frame objects are written to the RPC transport — route logs/diagnostics elsewhere.
  3. Validate output on the sender side: JSON.parse(line) must yield an object before writing.

Example fix

// before
write(JSON.stringify("pong"));
// after
write(JSON.stringify({ type: "response", id, success: true, result: "pong" }));
Defensive patterns

Strategy: type-guard

Validate before calling

// pre-write validation on the sender side
const line = JSON.stringify(frame);
if (typeof JSON.parse(line) !== "object" || JSON.parse(line) === null || Array.isArray(JSON.parse(line))) {
  throw new Error("RPC frame must be a JSON object");
}

Type guard

function isRpcFrameObject(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}

Try / catch

try {
  decoder.push(parsedLine);
} catch (err) {
  if (err instanceof Error && err.message === "rpc frame must be an object") {
    logger.warn("ignoring non-object RPC line", { line: truncated(parsedLine) });
  } else throw err;
}

Prevention

When it happens

Trigger: Writing raw JSON values like `"hello"`, `42`, `null`, or `[1,2]` as RPC lines; a sender that JSON-stringifies a bare value instead of a frame object; a corrupt/truncated line that still parses as a scalar.

Common situations: Custom scripts piping debug output into the RPC stdin; a client SDK bug that drops the frame wrapper object; log lines accidentally written to the RPC channel.

Related errors


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