can1357/oh-my-pi · error

rpc chunk sequence exceeds declared length

Error message

rpc chunk sequence exceeds declared length

What it means

As chunks accumulate, push tracks receivedBytes against the sequence's declared byteLength. This error is thrown as soon as accumulated chunk payload bytes exceed the declared total, proving the frame's byteLength header lied (or an extra oversized chunk slipped in) — caught before any full-payload allocation.

Source

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

		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,
	streamedMessages?: readonly unknown[],
): object {
	if (!isRecord(frame) || frame.type !== "agent_end" || !Array.isArray(frame.messages)) return frame;
	let streamed = Number.isSafeInteger(streamedMessageCount)

View on GitHub (pinned to 9690622007)

Solutions

  1. Compute byteLength with Buffer.byteLength(json, 'utf8'), never json.length.
  2. Ensure sum of per-chunk decoded byte counts equals byteLength exactly — no extra or oversized chunks.
  3. Compute byteLength from the same exact bytes that are sliced into chunks (serialize once, reuse the buffer).

Example fix

// before: character count, not bytes
const byteLength = json.length;
// after: UTF-8 byte length
const byteLength = Buffer.byteLength(json, "utf8");
Defensive patterns

Strategy: validation

Validate before calling

// sender-side pre-flight: byteLength must be exact UTF-8 byte count
const bytes = Buffer.from(json, "utf8");
const byteLength = bytes.byteLength; // NOT json.length
let total = 0;
for (let i = 0; i < byteLength; i += 256 * 1024) {
  total += Math.min(256 * 1024, byteLength - i);
}
if (total !== byteLength) throw new Error("chunk split does not sum to byteLength");

Try / catch

try {
  const frame = decoder.push(parsedLine);
} catch (err) {
  if (err instanceof Error && err.message === "rpc chunk sequence exceeds declared length") {
    logger.error("sender byteLength header understates payload; discarding sequence");
    decoder = new RpcFrameDecoder();
  } else throw err;
}

Prevention

When it happens

Trigger: Sender declaring byteLength smaller than the actual UTF-8 size of the logical frame (e.g. using .length characters instead of Buffer.byteLength for multi-byte UTF-8); an extra chunk appended beyond count; oversized chunks that individually pass the 256KiB check but sum past the header.

Common situations: Hand-rolled senders measuring the JSON string with string length instead of byte length (CJK/emoji payloads inflate UTF-8 size); byteLength computed on the base64 form instead of raw bytes; duplicate chunk sends inflating receivedBytes.

Related errors


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