{"record":{"id":"a284a5f83eb0c438","repo":"chroma-core/chroma","slug":"limit-offset-must-be-non-negative-got-offset","errorCode":null,"errorMessage":"Limit offset must be non-negative, got {offset}","messagePattern":"Limit offset must be non-negative, got (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"chromadb/execution/expression/operator.py","lineNumber":575,"sourceCode":"    @staticmethod\n    def from_dict(data: Dict[str, Any]) -> \"Limit\":\n        \"\"\"Create Limit from dictionary.\n\n        Examples:\n        - {\"offset\": 10} -> Limit(offset=10)\n        - {\"offset\": 10, \"limit\": 20} -> Limit(offset=10, limit=20)\n        - {\"limit\": 20} -> Limit(offset=0, limit=20)\n        \"\"\"\n        if not isinstance(data, dict):\n            raise TypeError(f\"Expected dict for Limit, got {type(data).__name__}\")\n\n        offset = data.get(\"offset\", 0)\n        if not isinstance(offset, int):\n            raise TypeError(\n                f\"Limit offset must be an integer, got {type(offset).__name__}\"\n            )\n        if offset < 0:\n            raise ValueError(f\"Limit offset must be non-negative, got {offset}\")\n\n        limit = data.get(\"limit\")\n        if limit is not None:\n            if not isinstance(limit, int):\n                raise TypeError(\n                    f\"Limit limit must be an integer, got {type(limit).__name__}\"\n                )\n            if limit <= 0:\n                raise ValueError(f\"Limit limit must be positive, got {limit}\")\n\n        # Check for unexpected keys\n        allowed_keys = {\"offset\", \"limit\"}\n        unexpected_keys = set(data.keys()) - allowed_keys\n        if unexpected_keys:\n            raise ValueError(f\"Unexpected keys in Limit dict: {unexpected_keys}\")\n\n        return Limit(offset=offset, limit=limit)\n","sourceCodeStart":557,"sourceCodeEnd":593,"githubUrl":"https://github.com/chroma-core/chroma/blob/aecdd12c8a891610db8653630b066b32ceb678b5/chromadb/execution/expression/operator.py#L557-L593","documentation":"Limit.from_dict rejects negative offsets with ValueError: an offset counts records to skip from the start, so any value below 0 is meaningless. The check is offset < 0, run after the int type check, so only integers can reach it.","triggerScenarios":"Limit.from_dict({'offset': -1}); page arithmetic going below zero, e.g. {'offset': (page - 1) * page_size} with page=0; cursor logic like total - fetched when fetched > total.","commonSituations":"1-based page counters fed into a 0-based formula; clients sending page=0 to an API that computes offset=(page-1)*size; paging state carried between requests and underflowing on the short last page.","solutions":["Clamp the computed offset: offset = max(0, (page - 1) * page_size).","Treat page 0 and page 1 the same: page = max(page, 1).","Validate user-supplied page/offset params at the API edge before building the Search payload."],"exampleFix":"# before\noffset = (page - 1) * page_size       # page=0 -> -10 -> ValueError\nSearch(limit={'offset': offset, 'limit': page_size})\n\n# after\noffset = max(0, (page - 1) * page_size)\nSearch(limit={'offset': offset, 'limit': page_size})","handlingStrategy":"validation","validationCode":"def safe_offset(page: int, page_size: int) -> int:\n    return max(0, (max(page, 1) - 1) * page_size)","typeGuard":null,"tryCatchPattern":"try:\n    Search(limit=limit_cfg)\nexcept ValueError as e:\n    if 'offset' in str(e):\n        limit_cfg = {**limit_cfg, 'offset': 0}  # or return 400 to the client\n    else:\n        raise","preventionTips":["Never trust client-supplied page numbers; clamp before computing offsets.","Add a boundary test for page=0 and page=1.","Log the offending offset together with the failing payload for quick diagnosis."],"tags":["validation","valueerror","pagination","offset","chromadb"],"backgroundTag":"out-of-range-value","analyzedSha":"aecdd12c8a891610db8653630b066b32ceb678b5","analyzedAt":"2026-08-16T21:53:27.228Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}