{"record":{"id":"97a831b621a5dd1f","repo":"can1357/oh-my-pi","slug":"rpc-chunk-sequence-length-mismatch-97a831","errorCode":null,"errorMessage":"RPC chunk sequence length mismatch","messagePattern":"RPC chunk sequence length mismatch","errorType":"exception","errorClass":"RpcError","httpStatus":null,"severity":"error","filePath":"python/omp-rpc/src/omp_rpc/client.py","lineNumber":206,"sourceCode":"                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\")\n\n        self._pending = None\n        try:\n            decoded = b\"\".join(pending.chunks).decode(\"utf-8\")\n            frame = json.loads(decoded)\n        except (UnicodeDecodeError, json.JSONDecodeError) as exc:\n            raise RpcError(\"Failed to decode reassembled RPC frame\") from exc\n        if not isinstance(frame, dict):\n            raise RpcError(\"RPC frame must be a JSON object\")\n        return cast(JsonObject, frame)\n\n\ndef _process_group_id(process: subprocess.Popen[Any]) -> int | None:\n    \"\"\"Process-group id of `process`, or `None` when groups are unavailable.\n\n    Captured right after spawn so teardown can signal the whole group even\n    after the leader is reaped — POSIX `os.getpgid` fails on a reaped pid.\n    \"\"\"","sourceCodeStart":188,"sourceCodeEnd":224,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/python/omp-rpc/src/omp_rpc/client.py#L188-L224","documentation":"All chunks of the sequence arrived (next_index reached count) but the accumulated byte total does not equal the declared byteLength — the sequence is short (received < declared). Chunk counts matched yet bytes don't, meaning chunks were consistent in metadata but the sender's declared size or last-chunk content was wrong. The reassembler requires an exact byte match before decoding the frame.","triggerScenarios":"push() of the final chunk of a sequence where pending.received_bytes != pending.byte_length — the sender declared a larger byteLength than the actual payload it chunked (short read, truncated payload, inflated declared size).","commonSituations":"Sender read the payload from a file/stream and hit EOF early while declaring the expected size; sender padded byteLength to a boundary; byteLength computed from a different (newer/older) version of the payload than the one chunked; empty-chunk edge cases.","solutions":["Assert on the sender: sum(len(c) for c in chunks) == byteLength before sending; fix the computation if not.","Re-read the source payload fully (ensure no truncated file/stream read) and re-chunk.","Derive byteLength from the chunks actually produced, not from a precomputed expected size.","Enable logging of declared vs received byte counts on both ends to pinpoint which side miscounts."],"exampleFix":"// before\nbyte_length = expected_size          # from metadata, may be stale\nchunks = split(payload_bytes)\nsend(chunks, byte_length)            # short payload -> mismatch\n\n// after\nchunks = split(payload_bytes)\nbyte_length = sum(len(c) for c in chunks)  # derived from actual chunks\nassert byte_length == len(payload_bytes)\nsend(chunks, byte_length)","handlingStrategy":"validation","validationCode":"def verify_before_send(chunks: list[bytes], byte_length: int) -> None:\n    received = sum(len(c) for c in chunks)\n    if received != byte_length:\n        raise ValueError(f\"declared {byte_length} but chunks total {received}\")","typeGuard":"def is_complete_sequence(chunks: list[bytes], byte_length: int) -> bool:\n    return sum(len(c) for c in chunks) == byte_length","tryCatchPattern":"try:\n    frame = client.push(frame)\nexcept RpcError as exc:\n    if str(exc) == \"RPC chunk sequence length mismatch\":\n        logger.error(\"sequence complete but bytes=%d != declared=%d\", received_bytes, declared_bytes)\n        log_debug_counts_and_request_resend()\n    else:\n        raise","preventionTips":["Compute byteLength from the chunks actually produced, not a pre-estimated size.","Read the full source payload before chunking (no streaming truncation).","Log declared vs received byte counts on both ends during development."],"tags":["rpc","chunking","integrity","protocol"],"backgroundTag":"chunk-sequence-length-mismatch","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}