odysseus-dev/odysseus · warning · HTTPException
Invalid limit
Error message
Invalid limit
What it means
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.
Source
Thrown at routes/codex_routes.py:144
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")
memory_list_endpoint = _find_endpoint(memory_router, "GET", "/api/memory")
memory_add_endpoint = _find_endpoint(memory_router, "POST", "/api/memory/add")
calendar_list_events = _find_endpoint(calendar_router, "GET", "/api/calendar/events")
calendar_create_event = _find_endpoint(calendar_router, "POST", "/api/calendar/events")View on GitHub (pinned to f9235ebbf1)
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)).
Example fix
// before
fetch(`/api/codex/todos?limit=${selected}`) // selected === 'All'
// after
const limit = Number.isInteger(selected) ? Math.min(50, selected) : 50;
fetch(`/api/codex/todos?limit=${limit}`) Defensive patterns
Strategy: validation
Validate before calling
function safeLimit(v: unknown): number {
const n = Number(v);
return Number.isInteger(n) ? Math.min(50, Math.max(1, n)) : 50;
}
const url = `/api/codex/todos?limit=${safeLimit(rawLimit)}`; Type guard
function isLimitInt(v: unknown): v is number {
return typeof v === 'number' && Number.isInteger(v) && v >= 1 && v <= 50;
} Try / catch
try { r = await get(url) } catch (e) { if (e.status === 400 && e.detail === 'Invalid limit') { retry with limit=50 } else throw } Prevention
- 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.
When it happens
Trigger: GET /api/codex/...?limit=all, ?limit=10.0, ?limit=-Infinity, or repeated limit params collapsing to a list.
Common situations: UI dropdown with a textual 'All' option mapped straight to the query; float limits from page-size calculations; stringified 'null'/'NaN' from JS clients.
Related errors
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/10059a1dba8f5557.
Report an issue: GitHub.