{"record":{"id":"6968faf9a657a4ea","repo":"chroma-core/chroma","slug":"max-retries-must-be-a-non-negative-integer","errorCode":null,"errorMessage":"max_retries must be a non-negative integer","messagePattern":"max_retries must be a non-negative integer","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"chromadb/api/models/ConditionalCollectionTransaction.py","lineNumber":38,"sourceCode":"    Where,\n    WhereDocument,\n)\n\nif TYPE_CHECKING:\n    from chromadb.api.models.Collection import Collection\n\n\nT = TypeVar(\"T\")\n_RUN_RETRYABLE_ERRORS = (\n    ConditionalWriteConflictError,\n    StaleReadError,\n    BackoffError,\n)\n\n\ndef _validate_max_retries(max_retries: int) -> None:\n    if not isinstance(max_retries, int) or max_retries < 0:\n        raise ValueError(\"max_retries must be a non-negative integer\")\n\n\nclass ConditionalCollectionTransaction:\n    \"\"\"Collection-scoped optimistic transaction.\n\n    Reads execute immediately and capture the transaction snapshot. Writes are\n    buffered locally until ``commit()`` or until ``run(...)`` commits after a\n    successful callback.\n\n    Current limitations: transactions cannot span collections, nested\n    transaction guarantees are not provided, ``txn.query(...)`` and predicate\n    deletes are not supported, reading an ID after buffering a write for that\n    ID is an explicit transaction error, only one write per ID can be buffered,\n    and filter reads protect only returned IDs.\n    \"\"\"\n\n    def __init__(self, collection: \"Collection\") -> None:\n        self._collection = collection","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/chroma-core/chroma/blob/aecdd12c8a891610db8653630b066b32ceb678b5/chromadb/api/models/ConditionalCollectionTransaction.py#L20-L56","documentation":"`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.","triggerScenarios":"`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.","commonSituations":"Reading retry counts from environment variables or YAML (arrive as strings), or using -1 as an 'infinite retries' sentinel from another library's convention.","solutions":["Pass a non-negative int: `txn.run(cb, max_retries=5)`","Coerce external config: `max_retries = int(max_retries)` and validate the range at load time","If unlimited retries are wanted, pick an explicit high bound — 'infinite' is not supported"],"exampleFix":"# before\nretries = os.environ.get(\"TXN_RETRIES\", \"3\")  # str\ntxn.run(work, max_retries=retries)            # ValueError (str) \ntxn.run(work, max_retries=-1)                 # ValueError (negative)\n\n# after\nretries = int(os.environ.get(\"TXN_RETRIES\", \"3\"))\nassert retries >= 0\ntxn.run(work, max_retries=retries)","handlingStrategy":"validation","validationCode":"max_retries = int(max_retries) if str(max_retries).isdigit() else 3\nif not isinstance(max_retries, int) or max_retries < 0:\n    raise ValueError(\"max_retries must be a non-negative int\")\ntxn.run(work, max_retries=max_retries)","typeGuard":"def is_valid_max_retries(v) -> bool:\n    return isinstance(v, int) and not isinstance(v, bool) and v >= 0","tryCatchPattern":null,"preventionTips":["Parse retry settings from config/env into ints at load time with range validation","Do not use -1 for infinite retries — pick an explicit bound","Keep max_retries modest (e.g. 3-5); each retry re-runs the whole callback"],"tags":["transactions","retry","input-validation","configuration"],"backgroundTag":"invalid-argument-value","analyzedSha":"aecdd12c8a891610db8653630b066b32ceb678b5","analyzedAt":"2026-08-16T21:53:27.228Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}