can1357/oh-my-pi · error

rpc chunk sequence mismatch

Error message

rpc chunk sequence mismatch

What it means

Once reassembly is in progress, every subsequent rpc_chunk must match the pending sequence exactly: same chunkId, count, and byteLength, and its index must equal the expected nextIndex. This error is thrown when any of those fields differs — the chunk belongs to a different sequence, the header was mutated, or chunks arrived out of order or duplicated.

Source

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

			byteLength < MAX_RPC_FRAME_BYTES ||
			byteLength > MAX_RPC_REASSEMBLED_BYTES
		)
			throw new Error("invalid rpc chunk metadata");
		const bytes = decodeBase64(value.data);
		if (bytes.byteLength > RPC_CHUNK_PAYLOAD_BYTES) throw new Error("rpc chunk payload exceeds the transport limit");

		if (!this.#pending) {
			if (index !== 0) throw new Error("rpc chunk sequence must start at index 0");
			this.#pending = { chunkId, count, byteLength, nextIndex: 0, chunks: [], receivedBytes: 0 };
		}
		const pending = this.#pending;
		if (
			pending.chunkId !== chunkId ||
			pending.count !== count ||
			pending.byteLength !== byteLength ||
			pending.nextIndex !== index
		)
			throw new Error("rpc chunk sequence mismatch");
		pending.chunks.push(bytes);
		pending.receivedBytes += bytes.byteLength;
		pending.nextIndex++;
		if (pending.receivedBytes > pending.byteLength) throw new Error("rpc chunk sequence exceeds declared length");
		if (pending.nextIndex < pending.count) return undefined;
		if (pending.receivedBytes !== pending.byteLength) throw new Error("rpc chunk sequence length mismatch");

		this.#pending = undefined;
		const decoded = new TextDecoder("utf-8", { fatal: true }).decode(Buffer.concat(pending.chunks));
		const frame: unknown = JSON.parse(decoded);
		if (!isRecord(frame)) throw new Error("rpc frame must be an object");
		return frame;
	}
}

function compactTerminalFrame(
	frame: object,
	streamedMessageCount: number,

View on GitHub (pinned to 9690622007)

Solutions

  1. Send chunks strictly in index order, exactly once per sequence, on a single stream.
  2. Keep chunkId/count/byteLength immutable for the whole sequence — compute them once before emitting chunk 0.
  3. On a failed/retried send, restart the sequence with a NEW chunkId from index 0 and reset the decoder.
  4. Serialize chunk emission with a queue if multiple producers share the stream.

Example fix

// before: retry reuses old chunkId mid-stream
write(frameChunks); // first attempt partially sent
write(frameChunks); // duplicate chunkId, wrong nextIndex
// after: new sequence id and decoder reset
const chunkId = `rpc-${crypto.randomUUID()}`;
write(frameChunks.map(c => ({ ...c, chunkId })));
Defensive patterns

Strategy: try-catch

Validate before calling

// client-side sequencing check before writing the next chunk
if (pendingState && (chunk.chunkId !== pendingState.chunkId || chunk.index !== pendingState.nextIndex)) {
  throw new Error("sender bug: chunk does not continue the open sequence");
}

Try / catch

try {
  const frame = decoder.push(parsedLine);
} catch (err) {
  if (err instanceof Error && err.message === "rpc chunk sequence mismatch") {
    decoder = new RpcFrameDecoder(); // discard stale sequence; re-request the logical frame
  } else throw err;
}

Prevention

When it happens

Trigger: Interleaving chunks from two concurrent chunked sends with different chunkIds; resending a chunk after a retry (duplicate index); sender recomputing count/byteLength mid-sequence; out-of-order delivery of chunk lines.

Common situations: Retrying a failed write without dropping decoder state; concurrent agent responses multiplexed on one stream; a buggy sender that re-serializes the frame after emitting early chunks.

Related errors


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