{"record":{"id":"1098b79c0d4ac7ac","repo":"RyanCodrai/turbovec","slug":"persisted-store-is-inconsistent-with-its-index-si","errorCode":null,"errorMessage":"persisted store is inconsistent with its index: side-car has {len(handle_list)} {what} handle(s) but the index holds {n_index}. The .tvim index and its JSON side-car are out of sync.","messagePattern":"persisted store is inconsistent with its index: side-car has (.+?) (.+?) handle\\(s\\) but the index holds (.+?)\\. The \\.tvim index and its JSON side-car are out of sync\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"critical","filePath":"turbovec-python/python/turbovec/_persist.py","lineNumber":458,"sourceCode":"        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=\"\n            f\"{int(next_u64)} is below the largest {what} handle in use \"\n            f\"({max(handle_list)}). Loading it would reissue live handles \"\n            f\"on the next write.\"\n        )","sourceCodeStart":440,"sourceCodeEnd":476,"githubUrl":"https://github.com/RyanCodrai/turbovec/blob/ccab9f325e6ce2a270a87daf01ae4e443bcf2d49/turbovec-python/python/turbovec/_persist.py#L440-L476","documentation":"The .tvim index and the JSON side-car are two halves of one persisted store; the side-car must hold exactly one handle per vector in the index. When the counts differ, the two files are out of sync (one was written, edited, truncated, or replaced without the other), so loading is aborted with ValueError.","triggerScenarios":"load / load_from_disk / from_persist_path on a pair where the side-car handle array length != number of records in the index: e.g. index rebuilt/saved without updating the side-car, side-car truncated by a crashed write, or a mismatched pair of files from different saves.","commonSituations":"Copying only one of the two files to another machine; an interrupted atomic_save from an older version; manually pruning index vectors without touching the side-car; restoring one file from an older backup.","solutions":["Treat the pair as atomic: restore both files from the same backup/save point.","Rebuild the store from source data if no consistent backup exists.","If you know which file is stale, regenerate it: re-export the side-car from the index or vice versa with the writing turbovec version.","Before loading, sanity-check: `len(handles) == index_size`; if not, abort and surface a user-facing corruption message."],"exampleFix":"// before\n# index holds 10 vectors, side-car has 8 handles -> load\nstore = turbovec.load('store.tvim')\n// after\n# restore matching pair first, or guard:\nif len(sidecar['handles']) != index_size:\n    raise RuntimeError('store pair out of sync; restore both files')\nstore = turbovec.load('store.tvim')","handlingStrategy":"validation","validationCode":"def pair_in_sync(index_size: int, handles) -> bool:\n    return len(list(handles)) == index_size\n# compare with the .tvim index record count before loading","typeGuard":null,"tryCatchPattern":"try:\n    store = turbovec.load(path)\nexcept ValueError as e:\n    if 'out of sync' in str(e):\n        restore_both_files_from_backup()\n    else:\n        raise","preventionTips":["Always copy/backup .tvim and its JSON side-car together, atomically.","Restore both files from the same save point, never just one.","Don't prune or rebuild the index outside the library API.","After any external manipulation, run the handle checks before loading."],"tags":["python","persistence","corruption","consistency"],"backgroundTag":"checksum-mismatch","analyzedSha":"ccab9f325e6ce2a270a87daf01ae4e443bcf2d49","analyzedAt":"2026-09-06T08:39:18.516Z","contentChangedAt":"2026-09-06T08:39:18.516Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}