{"record":{"id":"247e7572b4c49efe","repo":"odysseus-dev/odysseus","slug":"invalid-request-parameters","errorCode":null,"errorMessage":"Invalid request parameters","messagePattern":"Invalid request parameters","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"routes/chat_routes.py","lineNumber":1105,"sourceCode":"                _explicit_browser_intent = True\n                if chat_mode == \"chat\":\n                    chat_mode = \"agent\"\n                    auto_escalated = True\n                    _workspace_agent_intent = False\n                    logger.info(\"chat→agent auto-escalation: contextual browser/form follow-up\")\n            if not workspace and isinstance(message, str):\n                _auto_workspace, _ = _resolve_workspace_from_message_path(request, message)\n                if _auto_workspace:\n                    workspace = _auto_workspace\n                    chat_mode = \"agent\"\n                    auto_escalated = True\n                    _workspace_agent_intent = True\n                    allow_bash = \"true\"\n                    logger.info(\"chat→agent auto-escalation: explicit path workspace=%s\", workspace)\n        except SessionNotFoundError as e:\n            raise HTTPException(404, str(e))\n        except (ValueError, ValidationError):\n            raise HTTPException(400, \"Invalid request parameters\")\n\n        # ------------------------------------------------------------------ #\n        # Privilege gates that must fire BEFORE any LLM work / token spend.\n        #   1. allowed_models — reject if session.model isn't in the user's\n        #      configured allowlist (empty list = \"no restriction\").\n        #   2. max_messages_per_day — count user-role ChatMessage rows owned\n        #      by this user in the last UTC day; 429 if at/over the cap.\n        # Admins always have full privileges via get_privileges (returns\n        # ADMIN_PRIVILEGES wholesale) so this is a no-op for them.\n        _enforce_chat_privileges(request, sess)\n\n        # Ensure session has auth headers\n        resolve_session_auth(sess, session, owner=effective_user(request))\n\n        # Check for research_pending BEFORE mode persist overwrites it\n        do_research = str(use_research).lower() == \"true\"\n        if not do_research:\n            if get_session_mode(session) == 'research_pending':","sourceCodeStart":1087,"sourceCodeEnd":1123,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/chat_routes.py#L1087-L1123","documentation":"Generic 400 raised when the request-shaping phase raises ValueError or pydantic ValidationError — i.e. the submitted parameters are well-formed JSON but semantically invalid. The original exception is swallowed, so the 400 text does not say which field failed.","triggerScenarios":"POST /api/chat_stream with out-of-range or wrongly-typed options: invalid chat_mode, bad preset_id, malformed attachments metadata, or any enum/constraint pydantic rejects during coercion.","commonSituations":"Frontend updated to send new option values the backend schema doesn't accept; version skew between client and server after a deploy; hand-written payloads with wrong types (strings for booleans, etc.).","solutions":["Check server logs/stderr — the swallowed ValueError/ValidationError details are logged or traceable there; the response body alone won't say","Diff the payload against the current request schema (chat_mode, preset_id, attachments, flags) and fix the offending field","After a backend upgrade, refresh the client so option names/types match the new schema"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"const ALLOWED_MODES = new Set(['chat','agent','research']);\nif (chat_mode && !ALLOWED_MODES.has(chat_mode)) { fixMode(); return; }","typeGuard":"function isValidChatOptions(o: any): boolean {\n  return (!o.chat_mode || ['chat','agent','research'].includes(o.chat_mode))\n      && (!o.preset_id || typeof o.preset_id === 'string');\n}","tryCatchPattern":"try { await streamChat(...); } catch (e) { if (e.status === 400 && e.message === 'Invalid request parameters') { logPayloadForSchemaDiff(payload); } }","preventionTips":["Keep client option names/types in lockstep with the server schema after deploys","Log the full payload when this 400 hits — the server hides the field name"],"tags":["validation","pydantic","chat-stream","client-error"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}