can1357/oh-my-pi · error
rpc chunk sequence length mismatch
Error message
rpc chunk sequence length mismatch
What it means
RpcFrameDecoder.push() reassembles protocol-v2 rpc_chunk frames. After the final expected chunk arrives, the accumulated byte count must exactly equal the declared frame byteLength; this error means the chunks ended before or after declaring they would, i.e. the chunk stream's metadata (count/byteLength) is inconsistent with its payloads.
Source
Thrown at packages/coding-agent/src/modes/rpc/rpc-frame.ts:181
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,
streamedMessages?: readonly unknown[],
): object {
if (!isRecord(frame) || frame.type !== "agent_end" || !Array.isArray(frame.messages)) return frame;
let streamed = Number.isSafeInteger(streamedMessageCount)
? Math.min(Math.max(0, streamedMessageCount), frame.messages.length)
: 0;View on GitHub (pinned to 9690622007)
Solutions
- Fix the sender so each rpc_chunk's data decodes to exactly the advertised sizes: byteLength must equal the full original JSON payload's byte count and count must equal ceil(byteLength / 256KiB).
- Verify both sides negotiate the same RPC protocol version (1 or 2); a v1 plain frame arriving mid-chunk-sequence throws 'rpc chunk sequence interrupted' instead, so ensure no frames are interleaved.
- Discard the RpcFrameDecoder instance (or its #pending state) after any chunk error and restart reassembly from index 0 — the decoder is stateful and cannot recover mid-sequence.
- If messages are truncated by a pipe/proxy, raise the transport's line-size limit or switch to protocol v2 chunking on the sender instead of one giant JSONL line.
Example fix
// before (sender splits payload incorrectly)
const chunks = payload.match(/.{1,200000}/gs) ?? [];
// after
const bytes = Buffer.from(json, 'utf8');
const RPC_CHUNK_PAYLOAD_BYTES = 256 * 1024;
const count = Math.ceil(bytes.byteLength / RPC_CHUNK_PAYLOAD_BYTES);
for (let i = 0; i < count; i++) {
data: bytes.subarray(i * RPC_CHUNK_PAYLOAD_BYTES, (i + 1) * RPC_CHUNK_PAYLOAD_BYTES).toString('base64')
} Defensive patterns
Strategy: validation
Validate before calling
function isValidChunk(frame: unknown): boolean {
if (typeof frame !== "object" || frame === null) return false;
const f = frame as Record<string, unknown>;
return (
f.type === "rpc_chunk" &&
typeof f.chunkId === "string" && f.chunkId.length > 0 &&
Number.isSafeInteger(f.index) && Number.isSafeInteger(f.count) && Number.isSafeInteger(f.byteLength) &&
f.index >= 0 && f.index < f.count &&
typeof f.data === "string"
);
} Type guard
function isRpcChunkFrame(v: unknown): v is RpcChunkFrame {
return typeof v === "object" && v !== null && (v as Record<string, unknown>).type === "rpc_chunk";
} Try / catch
try {
const out = decoder.push(line);
if (out) handleFrame(out);
} catch (err) {
decoder = new RpcFrameDecoder(); // discard broken pending state
logger.error("chunk reassembly failed, restarting stream", { error: err });
} Prevention
- Use RpcFrameEncoder on the sender so count/byteLength always match the actual chunks
- Never interleave plain frames while a chunk sequence is in flight
- Negotiate identical protocol versions on both ends
- Recreate the decoder after any chunk error — pending state cannot resume
When it happens
Trigger: Pushing the last chunk (index === count-1) of an rpc_chunk sequence where sum of decoded base64 payload sizes !== byteLength declared in the chunk metadata — e.g. a chunk was dropped and replaced, or an encoder emitted fewer/shorter chunks than advertised.
Common situations: A custom or hand-rolled RPC client sends base64 payloads that don't total the declared byteLength; a proxy/truncating pipe drops or corrupts one chunk line; a protocol-version mismatch between encoder (v1 frames into a v2 decoder or vice versa) leaves a stale pending sequence.
Related errors
- Invalid RPC chunk metadata
- RPC chunk payload exceeds the transport limit
- RPC chunk sequence must start at index 0
- RPC chunk sequence length mismatch
- Replacement text is not valid UTF-8: {err}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/d425b06f7badd5e9.
Report an issue: GitHub.