{"record":{"id":"87321162957dbfd2","repo":"chroma-core/chroma","slug":"transactional-delete-for-id-id-requires-a-prio","errorCode":null,"errorMessage":"transactional delete for id \"{id}\" requires a prior read proving the id is present","messagePattern":"transactional delete for id \"(.+?)\" requires a prior read proving the id is present","errorType":"exception","errorClass":"InvalidArgumentError","httpStatus":400,"severity":"error","filePath":"chromadb/api/conditional_http.py","lineNumber":309,"sourceCode":"            if id in self._buffered_write_ids:\n                raise InvalidArgumentError(\n                    f'transaction already has a buffered write for id \"{id}\"'\n                )\n            self._validate_write_precondition(operation, id)\n\n    def _validate_write_precondition(self, operation: str, id: str) -> None:\n        if operation == \"add\" and id not in self._known_absent:\n            raise InvalidArgumentError(\n                f'transactional add for id \"{id}\" requires a prior read '\n                \"proving the id is absent\"\n            )\n        if operation == \"update\" and id not in self._known_present:\n            raise InvalidArgumentError(\n                f'transactional update for id \"{id}\" requires a prior read '\n                \"proving the id is present\"\n            )\n        if operation == \"delete\" and id not in self._known_present:\n            raise InvalidArgumentError(\n                f'transactional delete for id \"{id}\" requires a prior read '\n                \"proving the id is present\"\n            )\n\n\ndef require_conditional_http_transaction(\n    transaction: object,\n) -> ConditionalHttpTransaction:\n    if not isinstance(transaction, ConditionalHttpTransaction):\n        raise ValueError(\"invalid conditional transaction for HTTP client\")\n    return transaction\n\n\ndef _invalid_read_after_write(id: str) -> InvalidArgumentError:\n    return InvalidArgumentError(\n        f'cannot transactionally read id \"{id}\" after buffering a write for it'\n    )\n","sourceCodeStart":291,"sourceCodeEnd":327,"githubUrl":"https://github.com/chroma-core/chroma/blob/aecdd12c8a891610db8653630b066b32ceb678b5/chromadb/api/conditional_http.py#L291-L327","documentation":"Raised by _validate_write_precondition (chromadb/api/conditional_http.py:309) when a transactional delete targets an id the transaction has not proven present. Like add and update, delete follows the read-then-write protocol: only ids returned by a prior get inside the same transaction land in _known_present, and deleting an unobserved id is rejected. This makes deletes conflict-detectable via the OCC read token at commit time instead of silently no-oping.","triggerScenarios":"collection.delete(ids=[\"doc1\"]) inside a transaction with no prior get returning \"doc1\"; deleting based on a stale application-side existence check; the earlier get using where filters rather than explicit ids.","commonSituations":"Cleanup jobs that delete by a list of ids gathered outside the transaction; retrying a delete in a new transaction after a conflict without re-reading; id lists built from another system (CMS, queue) where some ids were already removed.","solutions":["Read the ids with an id-based get inside the same transaction, then delete only the ids that came back","Filter your id list against the get result before calling delete","Delete unobserved ids outside the transaction, or accept that transactional deletes require the read by design"],"exampleFix":"# before\nwith client.transaction():\n    collection.delete(ids=[\"gone\", \"here\"])\n# after\nwith client.transaction():\n    got = collection.get(ids=[\"gone\", \"here\"])\n    present = got[\"ids\"]\n    if present:\n        collection.delete(ids=present)","handlingStrategy":"validation","validationCode":"def txn_delete(collection, ids):\n    got = collection.get(ids=ids)          # proves presence inside the txn\n    present = list(got[\"ids\"])\n    if present:\n        collection.delete(ids=present)\n    return set(ids) - set(present)         # ids that were already absent","typeGuard":null,"tryCatchPattern":"from chromadb.errors import InvalidArgumentError\n\ntry:\n    collection.delete(ids=ids)\nexcept InvalidArgumentError as e:\n    if \"requires a prior read\" in str(e):\n        got = collection.get(ids=ids)\n        if got[\"ids\"]:\n            collection.delete(ids=list(got[\"ids\"]))\n    else:\n        raise","preventionTips":["Intersect your delete list with an id-based get result before deleting","On conflict-retry, re-read in the new transaction before re-deleting","Gather ids to delete via a transactional read, not from external state"],"tags":["chromadb","transaction","delete","read-before-write","occ"],"backgroundTag":"transaction-validation-failed","analyzedSha":"aecdd12c8a891610db8653630b066b32ceb678b5","analyzedAt":"2026-08-16T21:53:27.228Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}