{"record":{"id":"c232621bab3060e5","repo":"chroma-core/chroma","slug":"txn-commit-cannot-be-called-inside-run-c23262","errorCode":null,"errorMessage":"txn.commit() cannot be called inside run()","messagePattern":"txn\\.commit\\(\\) cannot be called inside run\\(\\)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"chromadb/api/models/AsyncConditionalCollectionTransaction.py","lineNumber":272,"sourceCode":"        delete_request = self._collection._validate_and_prepare_delete_request(\n            ids, None, None\n        )\n        if delete_request[\"ids\"] is None:\n            raise ValueError(\"ids must be provided for transactional delete\")\n\n        await self._run_transaction_operation(\n            self._collection._client._conditional_delete(\n                transaction=self._transaction,\n                collection_id=self._collection.id,\n                ids=delete_request[\"ids\"],\n                tenant=self._collection.tenant,\n                database=self._collection.database,\n            )\n        )\n\n    async def commit(self) -> ConditionalCommitResult:\n        if self._commit_blocked_by_run:\n            raise ValueError(\"txn.commit() cannot be called inside run()\")\n        return await self._commit_after_run()\n","sourceCodeStart":254,"sourceCodeEnd":274,"githubUrl":"https://github.com/chroma-core/chroma/blob/aecdd12c8a891610db8653630b066b32ceb678b5/chromadb/api/models/AsyncConditionalCollectionTransaction.py#L254-L274","documentation":"`run(callback)` sets an internal `_commit_blocked_by_run` flag and commits automatically after the callback returns successfully, retrying the whole callback on write conflicts. Calling `txn.commit()` from inside the callback would double-commit and break the retry protocol, so it is rejected with ValueError.","triggerScenarios":"Invoking `await txn.commit()` inside the callback passed to `collection.transaction.run(lambda txn: ...)` on an AsyncCollection, e.g. to make an early exit or partial save.","commonSituations":"Copy-pasting manual-transaction code (begin / do work / commit) into a `run()` block, or trying to conditionally commit halfway through a callback.","solutions":["Delete the `commit()` call from inside the callback; `run()` commits automatically when the callback returns","Return a value from the callback to signal outcomes; raise to abort without committing","If you truly need manual commit control, skip `run()` and use the explicit transaction flow (begin, operate, `commit()` outside any callback)"],"exampleFix":"# before\nasync def work(txn):\n    await txn.upsert(...)\n    await txn.commit()  # ValueError: inside run()\nawait collection.transaction.run(work)\n\n# after\nasync def work(txn):\n    await txn.upsert(...)\n    # run() commits automatically on return\nawait collection.transaction.run(work)","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    await txn.commit()\nexcept ValueError as e:\n    if \"cannot be called inside run()\" in str(e):\n        # structural bug: remove the commit; run() commits on callback return\n        raise","preventionTips":["Treat run() callbacks as commit-free: the library commits after the callback returns","Use explicit begin/commit transactions only outside run()","Signal early exit by raising or returning from the callback, never by committing"],"tags":["transactions","commit","api-misuse","async"],"backgroundTag":"invalid-operation-sequence","analyzedSha":"aecdd12c8a891610db8653630b066b32ceb678b5","analyzedAt":"2026-08-16T21:53:27.228Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}