{"record":{"id":"10059a1dba8f5557","repo":"odysseus-dev/odysseus","slug":"invalid-limit","errorCode":null,"errorMessage":"Invalid limit","messagePattern":"Invalid limit","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"routes/codex_routes.py","lineNumber":144,"sourceCode":"\ndef _find_endpoint(router: APIRouter | None, method: str, path: str):\n    if router is None:\n        return None\n    for route in getattr(router, \"routes\", []):\n        if getattr(route, \"path\", \"\") == path and method in getattr(route, \"methods\", set()):\n            return route.endpoint\n    return None\n\n\ndef _clamp_pagination(offset: Any, limit: Any, *, default_limit: int = 50, max_limit: int = 50) -> tuple[int, int]:\n    try:\n        parsed_offset = int(0 if offset in (None, \"\") else offset)\n    except (TypeError, ValueError):\n        raise HTTPException(400, \"Invalid offset\")\n    try:\n        parsed_limit = int(default_limit if limit in (None, \"\") else limit)\n    except (TypeError, ValueError):\n        raise HTTPException(400, \"Invalid limit\")\n    return max(0, parsed_offset), max(1, min(parsed_limit, max_limit))\n\n\ndef setup_codex_routes(\n    email_router: APIRouter | None = None,\n    memory_router: APIRouter | None = None,\n    calendar_router: APIRouter | None = None,\n    document_router: APIRouter | None = None,\n) -> APIRouter:\n    router = APIRouter(prefix=\"/api/codex\", tags=[\"codex\"])\n    email_list_endpoint = _find_endpoint(email_router, \"GET\", \"/api/email/list\")\n    email_read_endpoint = _find_endpoint(email_router, \"GET\", \"/api/email/read/{uid}\")\n    email_send_endpoint = _find_endpoint(email_router, \"POST\", \"/api/email/send\")\n    email_draft_endpoint = _find_endpoint(email_router, \"POST\", \"/api/email/draft\")\n    memory_list_endpoint = _find_endpoint(memory_router, \"GET\", \"/api/memory\")\n    memory_add_endpoint = _find_endpoint(memory_router, \"POST\", \"/api/memory/add\")\n    calendar_list_events = _find_endpoint(calendar_router, \"GET\", \"/api/calendar/events\")\n    calendar_create_event = _find_endpoint(calendar_router, \"POST\", \"/api/calendar/events\")","sourceCodeStart":126,"sourceCodeEnd":162,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/codex_routes.py#L126-L162","documentation":"Raised as HTTP 400 by _clamp_pagination when the limit query parameter cannot be parsed as an int. None/empty fall back to the default (50); values above max_limit are clamped, not rejected — only non-integer garbage triggers this 400.","triggerScenarios":"GET /api/codex/...?limit=all, ?limit=10.0, ?limit=-Infinity, or repeated limit params collapsing to a list.","commonSituations":"UI dropdown with a textual 'All' option mapped straight to the query; float limits from page-size calculations; stringified 'null'/'NaN' from JS clients.","solutions":["Send an integer limit between 1 and 50, or omit it to accept the default.","Map UI abstractions ('All') to the max_limit (50) instead of a string.","Sanitize client-side: const limit = Math.min(50, Math.max(1, parseInt(raw, 10) || 50))."],"exampleFix":"// before\nfetch(`/api/codex/todos?limit=${selected}`)  // selected === 'All'\n\n// after\nconst limit = Number.isInteger(selected) ? Math.min(50, selected) : 50;\nfetch(`/api/codex/todos?limit=${limit}`)","handlingStrategy":"validation","validationCode":"function safeLimit(v: unknown): number {\n  const n = Number(v);\n  return Number.isInteger(n) ? Math.min(50, Math.max(1, n)) : 50;\n}\nconst url = `/api/codex/todos?limit=${safeLimit(rawLimit)}`;","typeGuard":"function isLimitInt(v: unknown): v is number {\n  return typeof v === 'number' && Number.isInteger(v) && v >= 1 && v <= 50;\n}","tryCatchPattern":"try { r = await get(url) } catch (e) { if (e.status === 400 && e.detail === 'Invalid limit') { retry with limit=50 } else throw }","preventionTips":["Clamp client-side to 1..50 so the server clamp never matters.","Map UI labels like 'All' to the numeric max (50), never to a string.","Unit-test query builders against undefined/NaN inputs."],"tags":["http-400","pagination","query-params","validation"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}