{"record":{"id":"2507fe9972bd6207","repo":"chroma-core/chroma","slug":"transactional-add-for-id-id-requires-a-prior-r","errorCode":null,"errorMessage":"transactional add for id \"{id}\" requires a prior read proving the id is absent","messagePattern":"transactional add for id \"(.+?)\" requires a prior read proving the id is absent","errorType":"exception","errorClass":"InvalidArgumentError","httpStatus":400,"severity":"error","filePath":"chromadb/api/conditional_http.py","lineNumber":299,"sourceCode":"        self._operations.append({\"operation\": operation, \"payload\": payload})\n\n    def _validate_buffered_write(self, operation: str, ids: IDs) -> None:\n        call_ids: Set[str] = set()\n        for id in ids:\n            if id in call_ids:\n                raise InvalidArgumentError(\n                    f'transactional write request contains duplicate id \"{id}\"'\n                )\n            call_ids.add(id)\n            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:","sourceCodeStart":281,"sourceCodeEnd":317,"githubUrl":"https://github.com/chroma-core/chroma/blob/aecdd12c8a891610db8653630b066b32ceb678b5/chromadb/api/conditional_http.py#L281-L317","documentation":"Raised by _validate_write_precondition (chromadb/api/conditional_http.py:299) when a transactional add targets an id the transaction has not proven absent. The conditional transaction protocol is read-then-write: _known_absent is populated only by a prior id-based get (no where/where_document) inside the same transaction that did not return the id. This prevents blind inserts from racing with concurrent writes, since the commit's OCC check is anchored to the read set.","triggerScenarios":"Calling collection.add(ids=[\"doc1\"], ...) inside a transaction without a preceding collection.get(ids=[\"doc1\"]) in that same transaction; reading with a where filter first (which never marks ids absent); reading in a different or earlier transaction than the one used for the add.","commonSituations":"Porting non-transactional code that called add() directly; doing an existence check via get(where=...) or a query, which does not populate _known_absent; splitting read and write across two transaction objects.","solutions":["Inside the same transaction, first do an id-based get (collection.get(ids=[...]) with no where/where_document), then add the ids that were not returned","If you do not need insert-if-absent semantics, use upsert instead - it has no read precondition","Ensure the read and the add use the same transaction instance"],"exampleFix":"# before\nwith client.transaction():\n    collection.add(ids=[\"doc1\"], embeddings=[emb])   # no prior read\n# after\nwith client.transaction():\n    existing = collection.get(ids=[\"doc1\"])           # proves absence\n    collection.add(ids=[\"doc1\"], embeddings=[emb])","handlingStrategy":"validation","validationCode":"def prove_absent(collection, ids):\n    got = collection.get(ids=ids)          # id-based, no where filters\n    return [i for i in ids if i not in set(got[\"ids\"])]\n\nabsent = prove_absent(collection, new_ids)\n# only add() ids proven absent, inside the same transaction","typeGuard":null,"tryCatchPattern":"from chromadb.errors import InvalidArgumentError\n\ntry:\n    collection.add(ids=[\"doc1\"], embeddings=[emb])\nexcept InvalidArgumentError as e:\n    if \"requires a prior read\" in str(e):\n        collection.get(ids=[\"doc1\"])\n        collection.add(ids=[\"doc1\"], embeddings=[emb])\n    else:\n        raise","preventionTips":["Read (id-based get, no where) before every transactional add","If the record may exist, use upsert - it carries no read precondition","Keep the proving read and the add in the same transaction instance"],"tags":["chromadb","transaction","add","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"}