{"record":{"id":"c43e3730f0cc843c","repo":"can1357/oh-my-pi","slug":"invalid-rpc-chunk-metadata","errorCode":null,"errorMessage":"invalid rpc chunk metadata","messagePattern":"invalid rpc chunk metadata","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/modes/rpc/rpc-frame.ts","lineNumber":160,"sourceCode":"\t\t\tif (!isRecord(value)) throw new Error(\"rpc frame must be an object\");\n\t\t\treturn value;\n\t\t}\n\t\tconst { chunkId, index, count, byteLength } = value;\n\t\tif (\n\t\t\ttypeof chunkId !== \"string\" ||\n\t\t\tchunkId.length === 0 ||\n\t\t\tchunkId.length > 128 ||\n\t\t\t!Number.isSafeInteger(index) ||\n\t\t\t!Number.isSafeInteger(count) ||\n\t\t\t!Number.isSafeInteger(byteLength) ||\n\t\t\tindex < 0 ||\n\t\t\tcount < 2 ||\n\t\t\tcount > Math.ceil(MAX_RPC_REASSEMBLED_BYTES / RPC_CHUNK_PAYLOAD_BYTES) ||\n\t\t\tindex >= count ||\n\t\t\tbyteLength < MAX_RPC_FRAME_BYTES ||\n\t\t\tbyteLength > MAX_RPC_REASSEMBLED_BYTES\n\t\t)\n\t\t\tthrow new Error(\"invalid rpc chunk metadata\");\n\t\tconst bytes = decodeBase64(value.data);\n\t\tif (bytes.byteLength > RPC_CHUNK_PAYLOAD_BYTES) throw new Error(\"rpc chunk payload exceeds the transport limit\");\n\n\t\tif (!this.#pending) {\n\t\t\tif (index !== 0) throw new Error(\"rpc chunk sequence must start at index 0\");\n\t\t\tthis.#pending = { chunkId, count, byteLength, nextIndex: 0, chunks: [], receivedBytes: 0 };\n\t\t}\n\t\tconst pending = this.#pending;\n\t\tif (\n\t\t\tpending.chunkId !== chunkId ||\n\t\t\tpending.count !== count ||\n\t\t\tpending.byteLength !== byteLength ||\n\t\t\tpending.nextIndex !== index\n\t\t)\n\t\t\tthrow new Error(\"rpc chunk sequence mismatch\");\n\t\tpending.chunks.push(bytes);\n\t\tpending.receivedBytes += bytes.byteLength;\n\t\tpending.nextIndex++;","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/modes/rpc/rpc-frame.ts#L142-L178","documentation":"Before decoding payload data, push validates the rpc_chunk metadata: chunkId must be a 1–128 char string; index/count/byteLength must be safe integers with count in [2, ceil(64MiB/256KiB)], index < count, and byteLength in [1MiB, 64MiB]. This error is thrown when any of these constraints fails — the chunk header is structurally invalid.","triggerScenarios":"Hand-crafting rpc_chunk frames with wrong fields (e.g. count=1 for a small frame, byteLength of the chunk instead of the whole logical frame, missing byteLength); a sender whose logical frame is under 1 MiB (byteLength < MAX_RPC_FRAME_BYTES is rejected — only oversized frames are ever chunked); index ≥ count; non-integer or negative values.","commonSituations":"Implementing a third-party sender against protocol v2 and getting the header semantics wrong; sending a chunked frame for a small payload that should have been a single line; off-by-one in count; byteLength set per-chunk instead of per-logical-frame.","solutions":["Only chunk frames whose serialized size exceeds MAX_RPC_FRAME_BYTES; send everything else as one line.","Set byteLength to the UTF-8 byte length of the ENTIRE logical JSON frame, not the chunk payload.","Set count = ceil(byteLength / 256KiB) (always ≥ 2 for chunked frames) and index from 0 to count-1.","Keep chunkId a non-empty string of at most 128 characters, unique per logical frame."],"exampleFix":"// before: byteLength is the chunk size\n{ type: \"rpc_chunk\", chunkId, index, count, byteLength: chunk.length, data }\n// after: byteLength is the whole frame's UTF-8 length\nconst byteLength = Buffer.byteLength(json, \"utf8\");\nconst count = Math.ceil(byteLength / (256 * 1024));\n{ type: \"rpc_chunk\", chunkId, index, count, byteLength, data }","handlingStrategy":"validation","validationCode":"import { MAX_RPC_FRAME_BYTES, MAX_RPC_REASSEMBLED_BYTES } from \"./rpc-frame\";\nconst CHUNK = 256 * 1024;\nfunction validateChunkHeader(chunk: { chunkId: string; index: number; count: number; byteLength: number }): void {\n  if (!(chunk.chunkId.length > 0 && chunk.chunkId.length <= 128)) throw new Error(\"bad chunkId\");\n  if (chunk.count < 2 || chunk.count > Math.ceil(MAX_RPC_REASSEMBLED_BYTES / CHUNK)) throw new Error(\"bad count\");\n  if (chunk.index < 0 || chunk.index >= chunk.count) throw new Error(\"bad index\");\n  if (chunk.byteLength < MAX_RPC_FRAME_BYTES || chunk.byteLength > MAX_RPC_REASSEMBLED_BYTES) throw new Error(\"bad byteLength\");\n}","typeGuard":"function hasValidChunkMetadata(v: unknown): v is { type: \"rpc_chunk\"; chunkId: string; index: number; count: number; byteLength: number; data: string } {\n  return typeof v === \"object\" && v !== null &&\n    typeof (v as any).chunkId === \"string\" && (v as any).chunkId.length > 0 &&\n    Number.isSafeInteger((v as any).index) && Number.isSafeInteger((v as any).count) &&\n    Number.isSafeInteger((v as any).byteLength);\n}","tryCatchPattern":"try {\n  decoder.push(parsedLine);\n} catch (err) {\n  if (err instanceof Error && err.message === \"invalid rpc chunk metadata\") {\n    logger.error(\"sender produced invalid rpc_chunk header; aborting reassembly\", { parsedLine });\n  } else throw err;\n}","preventionTips":["Only chunk frames over 1 MiB; send smaller frames as single lines.","byteLength = Buffer.byteLength(entireFrameJson, 'utf8'), not per-chunk size.","count = ceil(byteLength / 262144); index strictly 0..count-1.","Reuse the library's own encoder (RpcFrameEncoder) rather than hand-crafting chunks."],"tags":["rpc","validation","protocol","metadata"],"backgroundTag":"invalid-rpc-chunk-metadata","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}