{"record":{"id":"927523e34c69c449","repo":"bytedance/deer-flow","slug":"model-model-name-r-is-not-in-the-configured-mode","errorCode":null,"errorMessage":"Model {model_name!r} is not in the configured model allowlist","messagePattern":"Model (.+?) is not in the configured model allowlist","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"backend/app/gateway/services.py","lineNumber":1097,"sourceCode":"    stream_modes = normalize_stream_modes(body.stream_mode)\n    bridge = get_stream_bridge(request)\n    run_mgr = get_run_manager(request)\n    run_ctx = get_run_context(request)\n\n    disconnect = DisconnectMode.cancel if body.on_disconnect == \"cancel\" else DisconnectMode.continue_\n\n    body_context = getattr(body, \"context\", None) or {}\n    model_name = body_context.get(\"model_name\")\n    # Coerce non-string model_name values to str before truncation.\n    if model_name is not None and not isinstance(model_name, str):\n        model_name = str(model_name)\n\n    # Validate model against the allowlist when a model_name is provided.\n    if model_name:\n        app_config = get_app_config()\n        resolved = app_config.get_model_config(model_name)\n        if resolved is None:\n            raise HTTPException(\n                status_code=400,\n                detail=f\"Model {model_name!r} is not in the configured model allowlist\",\n            )\n\n    owner_user_id = get_trusted_internal_owner_user_id(request)\n    # Stateless run endpoints carry thread_id in the request *body*, so the\n    # @require_permission(owner_check=True) decorator -- which resolves ownership\n    # from the path param -- cannot protect them. Enforce thread ownership here,\n    # before any run is created, so one user cannot start runs on (or read /wait\n    # checkpoint state from) another user's thread. Missing rows (auto-created\n    # temp threads) and NULL-owner rows (shared / pre-auth data) stay accessible\n    # via check_access; only a thread already owned by another user is rejected\n    # with 404, matching thread_runs.py's anti-enumeration behaviour. Internal\n    # channel runs act on behalf of the connection owner carried in\n    # X-DeerFlow-Owner-User-Id, so they are scoped to that owner instead of\n    # bypassing the check -- a leaked internal token must not grant cross-user\n    # thread access.\n    user = getattr(request.state, \"user\", None)","sourceCodeStart":1079,"sourceCodeEnd":1115,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/app/gateway/services.py#L1079-L1115","documentation":"HTTP 400 raised when the caller supplies context.model_name and the Gateway cannot resolve it via app_config.get_model_config(model_name). The Gateway enforces a model allowlist at request admission: any model_name not present in config.yaml's models list is rejected before the run starts, even though the lead agent itself would silently fall back to the default model.","triggerScenarios":"POST a run with body.context.model_name set to a name that is not a key in config.yaml models (typo, removed model, env-specific name), or a non-string value coerced to str (e.g. 42 -> '42').","commonSituations":"Frontend hardcodes a model name from another environment (dev vs prod config.yaml differ); a model was renamed or deleted from config.yaml; the user picked a model in the UI that the backend config never defined; typo like 'gpt-4o ' with whitespace.","solutions":["Compare the sent model_name against GET /api/models (or config.yaml models:) and use an exact configured name.","Fix typos/whitespace/case in the model_name value the client sends.","If the model should exist, add its full entry to config.yaml under models and restart the Gateway.","Omit context.model_name entirely to let the server use its default model."],"exampleFix":"# config.yaml\nmodels:\n  - name: gpt-4o        # client must send exactly \"gpt-4o\"\n    provider: openai\n    ...","handlingStrategy":"validation","validationCode":"const models = await api.listModels(); // names from config.yaml\nconst allowed = new Set(models.map(m => m.name));\nif (body.context?.model_name && !allowed.has(body.context.model_name)) {\n  delete body.context.model_name; // or pick allowed names interactively\n}","typeGuard":"const isConfiguredModel = (name, configured) => typeof name === 'string' && configured.some(m => m.name === name);","tryCatchPattern":"try { await api.createRun(threadId, body); } catch (e) { if (e.status === 400 && /allowlist/.test(e.detail)) { refresh model list, correct model_name, retry once; } throw e; }","preventionTips":["Drive the UI model picker from the server's model list, never a hardcoded list.","Treat model names as exact-match opaque ids (case/whitespace sensitive).","Restart Gateway after config.yaml model changes so all replicas agree."],"tags":["http","config","model","allowlist","api"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}