can1357/oh-my-pi · error

rpc chunk sequence interrupted

Error message

rpc chunk sequence interrupted

What it means

RpcFrameDecoder is a stateful reassembler: once it has received rpc_chunk frames for an in-progress logical frame (#pending set), the very next JSONL line must be the following chunk of that sequence. This error is thrown when a non-chunk frame arrives while a chunked sequence is still open, meaning the sequence was interrupted mid-stream.

Source

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

function decodeBase64(data: unknown): Buffer {
	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
		)

View on GitHub (pinned to 9690622007)

Solutions

  1. Emit all chunks of a logical frame back-to-back on the same stream with no interleaved frames.
  2. Serialize chunked-frame emission behind a lock/queue if multiple producers share the stream.
  3. On abort, discard the decoder state (create a new RpcFrameDecoder) or complete the sequence with a valid reassembly.

Example fix

// before: interleaving events between chunks
for (const c of chunks) write(c); write(otherFrame);
// after: emit chunks contiguously
for (const c of chunks) write(c); // then other frames
Defensive patterns

Strategy: try-catch

Try / catch

try {
  decoder.push(parsedLine);
} catch (err) {
  if (err instanceof Error && err.message === "rpc chunk sequence interrupted") {
    decoder = new RpcFrameDecoder(); // drop partial state; re-request the frame
  } else throw err;
}

Prevention

When it happens

Trigger: Interleaving a normal RPC frame (response, event, etc.) between rpc_chunk lines; a client sending two chunked frames concurrently; the sender aborting mid-sequence and sending a different frame without resetting the decoder.

Common situations: Multiplexing two agents' output onto one RPC stream; logging/heartbeat lines injected between chunks; replaying a partial capture that truncates a sequence then continues with other frames.

Related errors


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