{"record":{"id":"805d44ea3a1b050c","repo":"jamiepine/voicebox","slug":"limit-must-be-between-1-and-200","errorCode":null,"errorMessage":"`limit` must be between 1 and 200.","messagePattern":"`limit` must be between 1 and 200\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"backend/mcp_server/tools.py","lineNumber":184,"sourceCode":"            tmp.write(raw)\n            tmp_path = Path(tmp.name)\n        try:\n            return await _transcribe_file(tmp_path, language, model)\n        finally:\n            tmp_path.unlink(missing_ok=True)\n\n    @mcp.tool(\n        name=\"voicebox.list_captures\",\n        description=(\n            \"List recent voice captures (dictations, recordings, uploads) \"\n            \"with their transcripts. Most-recent first.\"\n        ),\n    )\n    async def voicebox_list_captures(\n        limit: int = 20, offset: int = 0\n    ) -> dict[str, Any]:\n        if not (1 <= limit <= 200):\n            raise ValueError(\"`limit` must be between 1 and 200.\")\n        if offset < 0:\n            raise ValueError(\"`offset` must be >= 0.\")\n        db = next(get_db())\n        try:\n            items, total = captures_service.list_captures(\n                db, limit=limit, offset=offset\n            )\n            return {\n                \"captures\": [\n                    item.model_dump(mode=\"json\") for item in items\n                ],\n                \"total\": total,\n            }\n        finally:\n            db.close()\n\n    @mcp.tool(\n        name=\"voicebox.list_profiles\",","sourceCodeStart":166,"sourceCodeEnd":202,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/mcp_server/tools.py#L166-L202","documentation":"Raised by voicebox.list_captures when the limit argument is outside the inclusive range [1, 200]. The check is `not (1 <= limit <= 200)`. Default is 20.","triggerScenarios":"Calling voicebox.list_captures(limit=0), limit=-1, limit=201, or a very large 'give me everything' value like limit=10000.","commonSituations":"Pagination UI sending limit=0 on an empty state; a 'fetch all' shortcut that sets a huge limit; off-by-one from a component that uses limit as 'last index'.","solutions":["Pass an integer between 1 and 200 (inclusive).","If you need more than 200 rows, page with offset (e.g. limit=200, offset=0, then offset=200).","Clamp client-side before calling: limit = max(1, min(200, requested))."],"exampleFix":"// before\nvoicebox.list_captures(limit=0)\n// after\nvoicebox.list_captures(limit=20)","handlingStrategy":"validation","validationCode":"if not isinstance(limit, int) or not (1 <= limit <= 200):\n    limit = max(1, min(200, int(limit) if isinstance(limit, int) else 20))\nawait voicebox.list_captures(limit=limit, offset=offset)","typeGuard":"def is_valid_list_limit(value: int) -> bool:\n    return isinstance(value, int) and 1 <= value <= 200","tryCatchPattern":"try:\n    await voicebox.list_captures(limit=limit, offset=offset)\nexcept ValueError as exc:\n    if \"limit\" in str(exc):\n        await voicebox.list_captures(limit=max(1, min(200, limit or 20)), offset=offset)\n    else:\n        raise","preventionTips":["Clamp client-side: limit = max(1, min(200, requested)).","Page through large result sets with offset rather than inflating limit.","Default UI page size to 20 to match the server default."],"tags":["mcp","pagination","validation","captures"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}