{"record":{"id":"f3505af02aa216e6","repo":"odysseus-dev/odysseus","slug":"invalid-offset","errorCode":null,"errorMessage":"Invalid offset","messagePattern":"Invalid offset","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"routes/codex_routes.py","lineNumber":140,"sourceCode":"    if not getattr(request.state, \"api_token\", False):\n        require_admin(request)\n    return owner\n\n\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\")","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/codex_routes.py#L122-L158","documentation":"Raised as HTTP 400 by _clamp_pagination when the offset query parameter cannot be parsed as an int — int() raises TypeError/ValueError on non-numeric strings, lists (repeated query params), dicts, or unhashable junk. Empty/None are treated as 0, so this fires only on genuinely malformed input.","triggerScenarios":"GET /api/codex/...?offset=abc, ?offset=1.5, ?offset=5&offset=10 (FastAPI yields a list), or a client serializing null as the string 'null'.","commonSituations":"Frontend interpolating an undefined variable into the query string ('offset=undefined'); passing a float page math result; copy-pasted URLs with stray characters.","solutions":["Send an integer offset, or omit the parameter entirely (defaults to 0).","In the client, coerce with Number.isInteger(Number(offset)) before building the URL.","Avoid sending the parameter twice; FastAPI turns repeated query keys into a list which fails int()."],"exampleFix":"// before\nfetch(`/api/codex/todos?offset=${page}`)  // page === undefined\n\n// after\nconst offset = Number.isInteger(page) ? page : 0;\nfetch(`/api/codex/todos?offset=${offset}`)","handlingStrategy":"validation","validationCode":"function safeOffset(v: unknown): number {\n  const n = Number(v);\n  return Number.isInteger(n) && n >= 0 ? n : 0;\n}\nconst url = `/api/codex/todos?offset=${safeOffset(rawOffset)}`;","typeGuard":"function isPaginationInt(v: unknown): v is number {\n  return typeof v === 'number' && Number.isInteger(v) && v >= 0;\n}","tryCatchPattern":"try { r = await get(url) } catch (e) { if (e.status === 400 && e.detail === 'Invalid offset') { retry with offset=0 } else throw }","preventionTips":["Never interpolate raw variables into query strings; coerce to integers with a default first.","Omit the parameter instead of sending 'undefined'/'null' strings.","Send each pagination parameter exactly once per request."],"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"}