chroma-core/chroma · error · ValueError

ids must be provided for transactional delete

Error message

ids must be provided for transactional delete

What it means

Synchronous twin of the async error: ConditionalCollectionTransaction.delete() only supports delete-by-ID. The request is built with where/where_document forced to None; if the validated ids come back None, the buffered-write transaction cannot proceed and raises before contacting the server.

Source

Thrown at chromadb/api/models/ConditionalCollectionTransaction.py:260

            lambda: self._collection._client._conditional_upsert(
                transaction=self._transaction,
                collection_id=self._collection.id,
                ids=upsert_request["ids"],
                embeddings=upsert_request["embeddings"],
                metadatas=upsert_request["metadatas"],
                documents=upsert_request["documents"],
                uris=upsert_request["uris"],
                tenant=self._collection.tenant,
                database=self._collection.database,
            )
        )

    def delete(self, ids: OneOrMany[ID]) -> None:
        delete_request = self._collection._validate_and_prepare_delete_request(
            ids, None, None
        )
        if delete_request["ids"] is None:
            raise ValueError("ids must be provided for transactional delete")

        self._run_transaction_operation(
            lambda: self._collection._client._conditional_delete(
                transaction=self._transaction,
                collection_id=self._collection.id,
                ids=delete_request["ids"],
                tenant=self._collection.tenant,
                database=self._collection.database,
            )
        )

    def commit(self) -> ConditionalCommitResult:
        if self._commit_blocked_by_run:
            raise ValueError("txn.commit() cannot be called inside run()")
        return self._commit_after_run()

View on GitHub (pinned to aecdd12c8a)

Solutions

  1. Pass an explicit list of IDs: `txn.delete(["id-1", "id-2"])`
  2. Collect IDs first with `txn.get(where=..., include=[])` and delete those in the same transaction
  3. Use non-transactional `collection.delete(where=...)` when predicate deletes are required

Example fix

# before
txn.delete(ids=None)

# after
if not ids:
    raise ValueError("transactional delete requires ids")
txn.delete(ids=ids)
Defensive patterns

Strategy: validation

Validate before calling

ids = list(ids or [])
if not ids:
    raise ValueError("transactional delete requires explicit non-empty ids")
txn.delete(ids=ids)

Type guard

from typing import Any, List, TypeGuard

def is_non_empty_id_list(v: Any) -> TypeGuard[List[str]]:
    return isinstance(v, (list, tuple)) and len(v) > 0 and all(isinstance(i, str) for i in v)

Prevention

When it happens

Trigger: Calling `txn.delete(ids)` inside `collection.transaction.run(...)` on a sync Collection with an ids value that passes the presence check but unpacks to None (None-equivalent/empty/malformed value).

Common situations: Moving filter-based deletes into optimistic transactions; passing a variable that is None at runtime inside the run callback.

Related errors


AI-assisted analysis of chroma-core/chroma@aecdd12c8a (2026-08-16). Data as JSON: /api/errors/bcf3d00c17779963. Report an issue: GitHub.