{"record":{"id":"de664b542954ae79","repo":"can1357/oh-my-pi","slug":"rpc-chunk-sequence-interrupted","errorCode":null,"errorMessage":"rpc chunk sequence interrupted","messagePattern":"rpc chunk sequence interrupted","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/modes/rpc/rpc-frame.ts","lineNumber":141,"sourceCode":"function decodeBase64(data: unknown): Buffer {\n\tif (\n\t\ttypeof data !== \"string\" ||\n\t\tdata.length === 0 ||\n\t\t!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(data)\n\t)\n\t\tthrow new Error(\"invalid rpc chunk data\");\n\tconst bytes = Buffer.from(data, \"base64\");\n\tif (bytes.toString(\"base64\") !== data) throw new Error(\"invalid rpc chunk data\");\n\treturn bytes;\n}\n\n/** Reassemble protocol v2 chunk frames after each JSONL line has been parsed. */\nexport class RpcFrameDecoder {\n\t#pending?: PendingRpcChunks;\n\n\tpush(value: unknown): object | undefined {\n\t\tif (!isRpcChunkFrame(value)) {\n\t\t\tif (this.#pending) throw new Error(\"rpc chunk sequence interrupted\");\n\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)","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/modes/rpc/rpc-frame.ts#L123-L159","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Emit all chunks of a logical frame back-to-back on the same stream with no interleaved frames.","Serialize chunked-frame emission behind a lock/queue if multiple producers share the stream.","On abort, discard the decoder state (create a new RpcFrameDecoder) or complete the sequence with a valid reassembly."],"exampleFix":"// before: interleaving events between chunks\nfor (const c of chunks) write(c); write(otherFrame);\n// after: emit chunks contiguously\nfor (const c of chunks) write(c); // then other frames","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  decoder.push(parsedLine);\n} catch (err) {\n  if (err instanceof Error && err.message === \"rpc chunk sequence interrupted\") {\n    decoder = new RpcFrameDecoder(); // drop partial state; re-request the frame\n  } else throw err;\n}","preventionTips":["Emit all chunks of a logical frame contiguously with no interleaved frames.","Funnel all stream writers through a single queue.","Recreate the decoder after any abort instead of continuing with stale state."],"tags":["rpc","protocol","state","stream-ordering"],"backgroundTag":"rpc-chunk-sequence-interrupted","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}