chroma-core/chroma · error · ValueError

max_retries must be a non-negative integer

Error message

max_retries must be a non-negative integer

What it means

`collection.transaction.run(callback, max_retries=...)` validates max_retries up front: it must be a Python int and >= 0 (bools also fail the value path since True==1 is int but a float or string is not). The value bounds how many times run() retries the whole callback on ConditionalWriteConflictError, StaleReadError, or BackoffError.

Source

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

    Where,
    WhereDocument,
)

if TYPE_CHECKING:
    from chromadb.api.models.Collection import Collection


T = TypeVar("T")
_RUN_RETRYABLE_ERRORS = (
    ConditionalWriteConflictError,
    StaleReadError,
    BackoffError,
)


def _validate_max_retries(max_retries: int) -> None:
    if not isinstance(max_retries, int) or max_retries < 0:
        raise ValueError("max_retries must be a non-negative integer")


class ConditionalCollectionTransaction:
    """Collection-scoped optimistic transaction.

    Reads execute immediately and capture the transaction snapshot. Writes are
    buffered locally until ``commit()`` or until ``run(...)`` commits after a
    successful callback.

    Current limitations: transactions cannot span collections, nested
    transaction guarantees are not provided, ``txn.query(...)`` and predicate
    deletes are not supported, reading an ID after buffering a write for that
    ID is an explicit transaction error, only one write per ID can be buffered,
    and filter reads protect only returned IDs.
    """

    def __init__(self, collection: "Collection") -> None:
        self._collection = collection

View on GitHub (pinned to aecdd12c8a)

Solutions

  1. Pass a non-negative int: `txn.run(cb, max_retries=5)`
  2. Coerce external config: `max_retries = int(max_retries)` and validate the range at load time
  3. If unlimited retries are wanted, pick an explicit high bound — 'infinite' is not supported

Example fix

# before
retries = os.environ.get("TXN_RETRIES", "3")  # str
txn.run(work, max_retries=retries)            # ValueError (str) 
txn.run(work, max_retries=-1)                 # ValueError (negative)

# after
retries = int(os.environ.get("TXN_RETRIES", "3"))
assert retries >= 0
txn.run(work, max_retries=retries)
Defensive patterns

Strategy: validation

Validate before calling

max_retries = int(max_retries) if str(max_retries).isdigit() else 3
if not isinstance(max_retries, int) or max_retries < 0:
    raise ValueError("max_retries must be a non-negative int")
txn.run(work, max_retries=max_retries)

Type guard

def is_valid_max_retries(v) -> bool:
    return isinstance(v, int) and not isinstance(v, bool) and v >= 0

Prevention

When it happens

Trigger: `txn.run(cb, max_retries=-1)`, `max_retries=1.5`, `max_retries="3"`, or `max_retries=None` — typically from config/env parsing that did not coerce to int.

Common situations: Reading retry counts from environment variables or YAML (arrive as strings), or using -1 as an 'infinite retries' sentinel from another library's convention.

Related errors


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