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

  1. Send an integer limit between 1 and 50, or omit it to accept the default.
  2. Map UI abstractions ('All') to the max_limit (50) instead of a string.
  3. 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

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.