{"record":{"id":"f2daf32b9eda8a94","repo":"odysseus-dev/odysseus","slug":"preset-payload-invalid-exc","errorCode":null,"errorMessage":"Preset payload invalid: {exc}","messagePattern":"Preset payload invalid: (.+?)","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"routes/codex_routes.py","lineNumber":812,"sourceCode":"            )\n        if chosen is None:\n            raise HTTPException(404, f\"No preset matching {name!r}\")\n        repo_id = chosen.get(\"model\") or chosen.get(\"modelId\") or \"\"\n        cmd = (chosen.get(\"cmd\") or \"\").strip()\n        host = chosen.get(\"host\") or chosen.get(\"remoteHost\") or \"\"\n        if not repo_id or not cmd or cmd.startswith(\"(adopted\"):\n            raise HTTPException(400, f\"Preset {chosen.get('name')!r} has no launchable cmd \"\n                                     \"(adopted from external launch). Use POST /cookbook/serve \"\n                                     \"with the actual cmd instead.\")\n        # Reuse the serve handler we already validated.\n        from routes.cookbook_helpers import ServeRequest\n        body = {\"repo_id\": repo_id, \"cmd\": cmd}\n        if host:\n            body[\"remote_host\"] = host\n        try:\n            req = ServeRequest(**body)\n        except Exception as exc:\n            raise HTTPException(400, f\"Preset payload invalid: {exc}\")\n        serve_endpoint = _find_endpoint(None, \"POST\", \"/api/model/serve\")\n        if serve_endpoint is None:\n            from fastapi import FastAPI\n            app: FastAPI = request.app\n            for route in app.routes:\n                if getattr(route, \"path\", None) == \"/api/model/serve\" and \"POST\" in getattr(route, \"methods\", set()):\n                    serve_endpoint = route.endpoint\n                    break\n        if serve_endpoint is None:\n            raise HTTPException(503, \"model serve endpoint unavailable\")\n        return await serve_endpoint(request, req)\n\n    @router.post(\"/cookbook/adopt\")\n    async def codex_cookbook_adopt(request: Request, body: dict[str, Any] = Body(default_factory=dict)):\n        \"\"\"Adopt an existing tmux session (one started via raw ssh+tmux) into\n        cookbook tracking. Needed when serve_model rejects a cmd and the\n        agent falls back to direct ssh — without adoption the session is\n        invisible to the UI. Body: {tmux_session, model, host?, port?}.\"\"\"","sourceCodeStart":794,"sourceCodeEnd":830,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/codex_routes.py#L794-L830","documentation":"Raised when constructing the pydantic ServeRequest from the preset's fields (repo_id, cmd, remote_host) raises — i.e. the stored preset payload fails model validation. The original exception text is appended, so the message tells you exactly which field is invalid (missing repo_id, bad host format, etc.).","triggerScenarios":"POST /cookbook/preset/{name} where chosen['model']/chosen['cmd']/chosen['host'] pass the loose checks but fail ServeRequest's stricter pydantic validation — e.g. remote_host not matching validate_remote_host rules, or cmd/repo_id rejected by field validators.","commonSituations":"Preset saved by an older version with a host format the current validator rejects; hand-edited state file; host alias containing characters the validator forbids.","solutions":["Read the {exc} suffix — it names the failing field; fix that field in the preset (via presets API or cookbook_state.json).","Re-save the preset through POST /cookbook/serve so it is validated by the current ServeRequest schema.","If the host is stale, update it to a currently valid remote host or clear it for local serving."],"exampleFix":"// before\n# preset stored remote_host = \"user@10.0.0.5:22\" (validator rejects)\n\n// after\n# fix stored preset host to the accepted form, e.g. \"user@10.0.0.5\"\nawait post('/cookbook/preset/' + name)","handlingStrategy":"validation","validationCode":"from routes.cookbook_helpers import ServeRequest\ntry:\n    ServeRequest(repo_id=repo_id, cmd=cmd, remote_host=host or None)\nexcept Exception as e:\n    fix_preset_fields(str(e))","typeGuard":null,"tryCatchPattern":"except HTTPException as e:\n    if e.status_code == 400 and 'Preset payload invalid' in e.detail:\n        # detail embeds the pydantic error; surface it to the preset editor\n        show_field_error(e.detail)","preventionTips":["Re-save presets after upgrading so the current schema revalidates them.","Run ServeRequest construction client-app-side (mirror of the schema) before launching."],"tags":["pydantic","validation","preset","http-400"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}