odysseus-dev/odysseus · warning · HTTPException
Invalid offset
Error message
Invalid offset
What it means
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.
Source
Thrown at routes/codex_routes.py:140
if not getattr(request.state, "api_token", False):
require_admin(request)
return owner
def _find_endpoint(router: APIRouter | None, method: str, path: str):
if router is None:
return None
for route in getattr(router, "routes", []):
if getattr(route, "path", "") == path and method in getattr(route, "methods", set()):
return route.endpoint
return None
def _clamp_pagination(offset: Any, limit: Any, *, default_limit: int = 50, max_limit: int = 50) -> tuple[int, int]:
try:
parsed_offset = int(0 if offset in (None, "") else offset)
except (TypeError, ValueError):
raise HTTPException(400, "Invalid offset")
try:
parsed_limit = int(default_limit if limit in (None, "") else limit)
except (TypeError, ValueError):
raise HTTPException(400, "Invalid limit")
return max(0, parsed_offset), max(1, min(parsed_limit, max_limit))
def setup_codex_routes(
email_router: APIRouter | None = None,
memory_router: APIRouter | None = None,
calendar_router: APIRouter | None = None,
document_router: APIRouter | None = None,
) -> APIRouter:
router = APIRouter(prefix="/api/codex", tags=["codex"])
email_list_endpoint = _find_endpoint(email_router, "GET", "/api/email/list")
email_read_endpoint = _find_endpoint(email_router, "GET", "/api/email/read/{uid}")
email_send_endpoint = _find_endpoint(email_router, "POST", "/api/email/send")
email_draft_endpoint = _find_endpoint(email_router, "POST", "/api/email/draft")View on GitHub (pinned to f9235ebbf1)
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().
Example fix
// before
fetch(`/api/codex/todos?offset=${page}`) // page === undefined
// after
const offset = Number.isInteger(page) ? page : 0;
fetch(`/api/codex/todos?offset=${offset}`) Defensive patterns
Strategy: validation
Validate before calling
function safeOffset(v: unknown): number {
const n = Number(v);
return Number.isInteger(n) && n >= 0 ? n : 0;
}
const url = `/api/codex/todos?offset=${safeOffset(rawOffset)}`; Type guard
function isPaginationInt(v: unknown): v is number {
return typeof v === 'number' && Number.isInteger(v) && v >= 0;
} Try / catch
try { r = await get(url) } catch (e) { if (e.status === 400 && e.detail === 'Invalid offset') { retry with offset=0 } else throw } Prevention
- 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.
When it happens
Trigger: 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'.
Common situations: Frontend interpolating an undefined variable into the query string ('offset=undefined'); passing a float page math result; copy-pasted URLs with stray characters.
Related errors
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/f3505af02aa216e6.
Report an issue: GitHub.