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
- Always wrap payloads in a frame object with a `type` field before JSON.stringify.
- Check that only frame objects are written to the RPC transport — route logs/diagnostics elsewhere.
- 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
- Wrap every payload in a typed frame object with a `type` field.
- Keep logs and diagnostics off the RPC transport channel.
- Validate parsed lines are objects before pushing to the decoder in adapters.
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
- Replacement text is not valid UTF-8: {err}
- invalid rpc chunk data
- invalid rpc chunk metadata
- rpc chunk payload exceeds the transport limit
- rpc chunk sequence exceeds declared length
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/debf25cdcd54631b.
Report an issue: GitHub.