chroma-core/chroma · error · ValueError
conditional transaction is closed
Error message
conditional transaction is closed
What it means
This ValueError comes from ConditionalHttpTransaction._ensure_open in chromadb/api/conditional_http.py: the conditional-transaction object has already been closed (its close() was called, typically after commit) and any further operation — prepare_get, record_get, buffer_add/update/delete/upsert, prepare_commit — raises 'conditional transaction is closed'. It is the Chroma equivalent of using a DB transaction after commit/rollback: the transaction is single-use.
Source
Thrown at chromadb/api/conditional_http.py:219
"operations": self._operations.copy(),
}
def prepare_commit_payload(
self,
) -> Optional[Tuple[ConditionalHttpScope, ConditionalHttpJsonPayload]]:
prepared_commit = self.prepare_commit()
if prepared_commit is None:
return None
scope = self._require_scope()
return (scope, prepared_commit)
def close(self, first_inserted_record_offset: Optional[int] = None) -> None:
self._ensure_open()
self._closed = True
def _ensure_open(self) -> None:
if self._closed:
raise ValueError("conditional transaction is closed")
def _record_scope(
self, collection_id: UUID, tenant: str, database: str
) -> ConditionalHttpScope:
scope = ConditionalHttpScope(str(collection_id), tenant, database)
if self._scope is None:
self._scope = scope
elif self._scope != scope:
raise ValueError("conditional transaction cannot span collections")
return scope
def _require_scope(self) -> ConditionalHttpScope:
if self._scope is None:
raise ValueError("conditional transaction has no collection scope")
return self._scope
def _validate_get_request(self, request_payload: ConditionalHttpGetPayload) -> None:
ids = request_payload.get("ids")View on GitHub (pinned to aecdd12c8a)
Solutions
- Create a NEW ConditionalHttpTransaction (via the client's transaction entry point) for each unit of work instead of reusing the closed one
- Track committed/closed state in your wrapper and refuse or reopen (new transaction) on subsequent writes
- Ensure retry logic acquires a fresh transaction after a commit failure or success, rather than replaying onto the same object
Example fix
# before tx = client.create_conditional_transaction() tx_add(tx, ids=[...]) client.commit(tx) # closes tx tx_add(tx, ids=['more']) # ValueError: conditional transaction is closed # after tx = client.create_conditional_transaction() tx_add(tx, ids=[...]) client.commit(tx) # tx closed here tx = client.create_conditional_transaction() # fresh transaction per unit of work tx_add(tx, ids=['more']) client.commit(tx)
Defensive patterns
Strategy: validation
Validate before calling
class TxnHandle:
def __init__(self, client):
self.client = client
self._tx = None
def begin(self):
self._tx = self.client.create_conditional_transaction() # or equivalent entry point
return self._tx
@property
def tx(self):
if self._tx is None or self._tx._closed:
self.begin() # fresh transaction; never reuse a closed one
return self._tx Type guard
def txn_is_open(tx) -> bool:
return getattr(tx, '_closed', False) is False Try / catch
try:
buffered_op(tx, ...)
except ValueError as e:
if 'conditional transaction is closed' in str(e):
tx = client.create_conditional_transaction() # start a new unit of work
buffered_op(tx, ...)
else:
raise Prevention
- Treat a conditional transaction as single-use: one begin -> ops -> commit cycle
- Wrap the transaction in a context manager that nulls the handle on commit/close
- Make retry logic acquire a fresh transaction instead of replaying onto the old object
- Do not cache transaction handles in long-lived singletons
When it happens
Trigger: Using the transaction API (client-style conditional transactions over HTTP) and calling any buffered operation after the transaction has been committed and closed, or after an explicit close(first_inserted_record_offset=...). Typical code: reuse of a stored transaction object across requests, or a loop that commits inside the body and keeps writing afterwards.
Common situations: Long-lived application objects holding a transaction handle across requests; retry wrappers that replay operations onto an already-committed transaction; frameworks where a per-client singleton transaction is accidentally shared; misunderstanding that close() finalizes the transaction permanently.
Related errors
- conditional transaction cannot span collections
- txn.commit() cannot be called inside run()
- Conditional transactions are only supported when connecting
- Embedding function provided when already defined in the coll
- Aggregate input must be an Aggregate instance or object with
AI-assisted analysis of chroma-core/chroma@aecdd12c8a (2026-08-16).
Data as JSON: /api/errors/8e4d3f75cb1a2103.
Report an issue: GitHub.