odysseus-dev/odysseus · error · HTTPException
{key} must be an integer
Error message
{key} must be an integer What it means
Raised as HTTP 400 by the settings-update endpoint when a request body key that is listed in _INT_RANGES cannot be coerced to int with int(val). The endpoint iterates DEFAULT_SETTINGS, so any numeric setting (e.g. a retry count or threshold) sent as a string like "abc", null, or a nested object triggers this. int() also rejects floats-with-junk ("1.5" raises ValueError) and None (TypeError).
Source
Thrown at routes/auth_routes.py:724
current = _load_settings()
# Per-key validation for numeric settings: coerce to int and clamp to a
# sane range so a bad value can't disable the agent or let it run away.
_INT_RANGES = {
"agent_max_rounds": (1, 200),
"agent_max_tool_calls": (0, 1000), # 0 = unlimited
}
for key in DEFAULT_SETTINGS:
if key in RETIRED_SETTING_KEYS:
continue
if key not in body:
continue
val = body[key]
if key in _INT_RANGES:
lo, hi = _INT_RANGES[key]
try:
val = int(val)
except (TypeError, ValueError):
raise HTTPException(400, f"{key} must be an integer")
val = max(lo, min(val, hi))
current[key] = val
_save_settings(current)
return without_retired_settings(current)
# ---- Integrations CRUD ----
# Run migration on startup
migrate_from_settings()
@router.get("/integrations")
async def list_integrations_route(request: Request):
"""List all integrations (admin only, keys masked)."""
user = _get_current_user(request)
if not user or not auth_manager.is_admin(user):
raise HTTPException(403, "Admin only")
items = load_integrations()
# Mask API keys for frontend displayView on GitHub (pinned to f9235ebbf1)
Solutions
- Coerce to integer on the client before sending: Math.trunc(Number(value)) and skip the key when the result is NaN.
- If fractional input is legitimate, send Math.round(value) or extend _INT_RANGES handling to accept floats.
- Check the key name in the 400 message — it names the exact setting that failed validation.
- Note the endpoint clamps to [lo, hi] after conversion, so only the type, not the range, matters here.
Example fix
// before
await fetch('/api/settings', {method:'PUT', body: JSON.stringify({max_retries: retryInput.value})});
// after
const n = Math.trunc(Number(retryInput.value));
if (!Number.isFinite(n)) throw new Error('max_retries must be an integer');
await fetch('/api/settings', {method:'PUT', body: JSON.stringify({max_retries: n})}); Defensive patterns
Strategy: validation
Validate before calling
const INT_SETTINGS = new Set(['max_retries', 'session_timeout', /* keys from _INT_RANGES */]);
function sanitizeSettings(body) {
for (const [k, v] of Object.entries(body)) {
if (INT_SETTINGS.has(k)) {
const n = Math.trunc(Number(v));
if (!Number.isFinite(n)) throw new Error(`${k} must be an integer`);
body[k] = n;
}
}
return body;
} Type guard
const isIntLike = (v: unknown): v is number => (typeof v === 'number' && Number.isInteger(v)) || (typeof v === 'string' && /^-?\d+$/.test(v.trim()));
Try / catch
try { await putSettings(body); } catch (e) {
if (e.status === 400 && /must be an integer/.test(e.message)) { /* flag the named field in the form */ }
else throw e;
} Prevention
- Always run numeric form inputs through Number() + Number.isFinite() before PUT.
- Keep a shared list of integer setting keys between frontend and backend.
- Never send null for an integer setting; omit the key instead.
When it happens
Trigger: PUT/PATCH the settings route with body {"<int-setting-key>": "twelve"}, {"<int-setting-key>": null}, or {"<int-setting-key>": {}}. Passing "1.5" (non-integer string) also raises. Keys not in _INT_RANGES or absent from body are skipped, so only ranged integer settings hit this path.
Common situations: Frontend form sending free-text input without Number() coercion; JSON null for an optional numeric field; a boolean or array serialized where an int was expected; locale-formatted numbers like "1 000".
Related errors
- Password is required
- Document is not linked to a source PDF
- Invalid point format
- Invalid box format
- msg_id and content are required
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/df8850dde167e77b.
Report an issue: GitHub.