{"record":{"id":"f7008d43c154b189","repo":"RyanCodrai/turbovec","slug":"persisted-store-is-corrupt-duplicate-what-handl","errorCode":null,"errorMessage":"persisted store is corrupt: duplicate {what} handles in the side-car","messagePattern":"persisted store is corrupt: duplicate (.+?) handles in the side-car","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"turbovec-python/python/turbovec/_persist.py","lineNumber":454,"sourceCode":"    Args:\n        index: the loaded ``IdMapIndex`` (uses ``len`` and ``contains``).\n        handles: the u64 handles the side-car maps can resolve.\n        what: noun for error messages (e.g. \"document\", \"node\").\n        next_u64: the side-car's handle watermark, if the caller has it.\n            Handles are issued by pre-incrementing it, so it must be at\n            least the largest handle in use; a smaller value reissues live\n            handles on the next write (issue #321).\n\n    Raises:\n        ValueError: if the side-car has duplicate handles, a different count\n            than the index, a handle the index doesn't contain, or a\n            watermark below the largest handle in use.\n    \"\"\"\n    handle_list = [int(h) for h in handles]\n    n_index = len(index)\n\n    if len(set(handle_list)) != len(handle_list):\n        raise ValueError(\n            f\"persisted store is corrupt: duplicate {what} handles in the side-car\"\n        )\n    if len(handle_list) != n_index:\n        raise ValueError(\n            f\"persisted store is inconsistent with its index: side-car has \"\n            f\"{len(handle_list)} {what} handle(s) but the index holds {n_index}. \"\n            f\"The .tvim index and its JSON side-car are out of sync.\"\n        )\n    for h in handle_list:\n        if not index.contains(h):\n            raise ValueError(\n                f\"persisted store is inconsistent with its index: a {what} in \"\n                f\"the side-car has no vector in the index (internal record id \"\n                f\"{h}). The .tvim index and its JSON side-car are out of sync.\"\n            )\n    if next_u64 is not None and handle_list and int(next_u64) < max(handle_list):\n        raise ValueError(\n            f\"persisted store is corrupt: the handle watermark next_u64=\"","sourceCodeStart":436,"sourceCodeEnd":472,"githubUrl":"https://github.com/RyanCodrai/turbovec/blob/ccab9f325e6ce2a270a87daf01ae4e443bcf2d49/turbovec-python/python/turbovec/_persist.py#L436-L472","documentation":"check_persisted_handles verifies that the handle list stored in the JSON side-car is sound relative to the .tvim vector index before loading. If the same handle appears twice the side-car is corrupt, and loading would map two records onto one handle, so it raises ValueError immediately.","triggerScenarios":"Calling load / load_from_disk / from_persist_path / _load_from (or check_persisted_handles directly) on a persisted pair whose docs- or metadata-handle array in the side-car contains a repeated integer, usually from manual editing, a botched merge of two files, or partial overwrite.","commonSituations":"Hand-editing or scripting the JSON side-car; concatenating/appending entries from two exports; a crashed external tool that duplicated lines; file-sync tools merging conflicting versions.","solutions":["Regenerate the store from source data (re-add the documents) rather than repairing the side-car by hand.","If repairing manually, remove duplicate handle entries and ensure the count matches the index (run the other checks: count and containment).","Restore the .tvim + JSON pair from backup.","Validate before load: `len(handles) == len(set(handles))` wrapped in try/except ValueError for a clean error message."],"exampleFix":"// before\n# side-car: \"handles\": [3, 5, 3]  -> duplicate\n// after\n# side-car: \"handles\": [3, 5]  (or restore from backup / rebuild store)","handlingStrategy":"validation","validationCode":"def handles_unique(handles) -> bool:\n    hs = [int(h) for h in handles]\n    return len(hs) == len(set(hs))\n# run on the side-car's handle arrays before load","typeGuard":null,"tryCatchPattern":"try:\n    store = turbovec.load(path)\nexcept ValueError as e:\n    if 'duplicate' in str(e) and 'handles' in str(e):\n        restore_from_backup()  # side-car is corrupt\n    else:\n        raise","preventionTips":["Never hand-edit the JSON side-car; use the library API for all mutations.","Keep .tvim and side-car together and back them up as a pair.","Checksum the side-car after every save and verify before load.","Reject external merges/syncs of side-car files (e.g. configure git/merge tools to treat them as binary)."],"tags":["python","persistence","corruption","data-integrity"],"backgroundTag":"checksum-mismatch","analyzedSha":"ccab9f325e6ce2a270a87daf01ae4e443bcf2d49","analyzedAt":"2026-09-06T08:39:18.516Z","contentChangedAt":"2026-09-06T08:39:18.516Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}