{"record":{"id":"f8a8fda672f5329b","repo":"can1357/oh-my-pi","slug":"rpc-chunk-sequence-must-start-at-index-0-f8a8fd","errorCode":null,"errorMessage":"RPC chunk sequence must start at index 0","messagePattern":"RPC chunk sequence must start at index 0","errorType":"exception","errorClass":"RpcError","httpStatus":null,"severity":"error","filePath":"python/omp-rpc/src/omp_rpc/client.py","lineNumber":188,"sourceCode":"            or index >= count\n            or byte_length < _MAX_RPC_FRAME_BYTES\n            or byte_length > _MAX_RPC_REASSEMBLED_BYTES\n            or not isinstance(data, str)\n            or not data\n        ):\n            raise RpcError(\"Invalid RPC chunk metadata\")\n        try:\n            chunk = base64.b64decode(data, validate=True)\n        except (binascii.Error, ValueError) as exc:\n            raise RpcError(\"Invalid RPC chunk data\") from exc\n        if base64.b64encode(chunk).decode(\"ascii\") != data:\n            raise RpcError(\"Invalid RPC chunk data\")\n        if len(chunk) > _RPC_CHUNK_PAYLOAD_BYTES:\n            raise RpcError(\"RPC chunk payload exceeds the transport limit\")\n\n        if self._pending is None:\n            if index != 0:\n                raise RpcError(\"RPC chunk sequence must start at index 0\")\n            self._pending = _PendingRpcChunks(chunk_id, count, byte_length)\n        pending = self._pending\n        if (\n            pending.chunk_id != chunk_id\n            or pending.count != count\n            or pending.byte_length != byte_length\n            or pending.next_index != index\n        ):\n            raise RpcError(\"RPC chunk sequence mismatch\")\n        pending.chunks.append(chunk)\n        pending.received_bytes += len(chunk)\n        pending.next_index += 1\n        if pending.received_bytes > pending.byte_length:\n            raise RpcError(\"RPC chunk sequence exceeds its declared length\")\n        if pending.next_index < pending.count:\n            return None\n        if pending.received_bytes != pending.byte_length:\n            raise RpcError(\"RPC chunk sequence length mismatch\")","sourceCodeStart":170,"sourceCodeEnd":206,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/python/omp-rpc/src/omp_rpc/client.py#L170-L206","documentation":"The client received the first chunk of a new sequence (no pending reassembly in progress), but its index is not 0. Chunk sequences must begin at index 0 so the reassembler can validate ordering and know the stream is complete. A mid-sequence chunk arriving without its head indicates frames were lost, dropped, or the pending state was reset between them.","triggerScenarios":"push() of an rpc_chunk with index > 0 while client._pending is None — e.g. the tail chunks of a sequence arrive after an earlier RpcError discarded the pending state, or the first chunk(s) were never delivered.","commonSituations":"An earlier chunk in the sequence failed validation (raising RpcError and aborting reassembly), leaving orphaned later chunks; frames filtered by a transport/queue; sender restart changed chunkId mid-sequence; a consumer mixing chunks from different sequences after an error.","solutions":["Make the sender send chunks 0..count-1 atomically with no filtering in between; retransmit the whole sequence on error.","After any RpcError during reassembly, discard ALL remaining chunks of that sequence (match chunkId) instead of continuing to push them.","Verify no middleware/queue drops the first frame of a burst.","Check that a client restart/reconnect resyncs with the sender so sequences restart cleanly."],"exampleFix":"// before\ntry:\n    for frame in frames:\n        client.push(frame)  # keeps pushing remaining chunks after an error\nexcept RpcError:\n    pass\n\n// after\nfor frame in frames:\n    try:\n        client.push(frame)\n    except RpcError:\n        current_chunk_id = frame.get(\"chunkId\") if isinstance(frame, dict) else None\n        frames = [f for f in frames if not (isinstance(f, dict) and f.get(\"chunkId\") == current_chunk_id)]\n        # drop rest of sequence, request resend","handlingStrategy":"try-catch","validationCode":"def safe_send_sequence(chunks: list[dict]) -> bool:\n    if not chunks or chunks[0].get(\"index\") != 0:\n        return False\n    return True","typeGuard":"def is_sequence_start(frame: dict) -> bool:\n    return isinstance(frame, dict) and frame.get(\"type\") == \"rpc_chunk\" and frame.get(\"index\") == 0","tryCatchPattern":"try:\n    client.push(frame)\nexcept RpcError as exc:\n    if str(exc) == \"RPC chunk sequence must start at index 0\":\n        logger.error(\"orphaned chunk (index=%s) with no pending sequence; dropping\", frame.get(\"index\"))\n        abort_sequence(frame.get(\"chunkId\"))  # drop remaining chunks, request full resend\n    else:\n        raise","preventionTips":["Send chunk sequences atomically; on any error, cancel the rest of the sequence.","Track in-flight chunkIds on the sender and restart the whole sequence on failure.","Never keep pushing queued chunk frames after the client reports an RpcError."],"tags":["rpc","chunking","ordering","protocol"],"backgroundTag":"chunk-sequence-out-of-order","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}