odysseus-dev/odysseus · warning · HTTPException
Preset payload invalid: {exc}
Error message
Preset payload invalid: {exc} What it means
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.).
Source
Thrown at routes/codex_routes.py:812
)
if chosen is None:
raise HTTPException(404, f"No preset matching {name!r}")
repo_id = chosen.get("model") or chosen.get("modelId") or ""
cmd = (chosen.get("cmd") or "").strip()
host = chosen.get("host") or chosen.get("remoteHost") or ""
if not repo_id or not cmd or cmd.startswith("(adopted"):
raise HTTPException(400, f"Preset {chosen.get('name')!r} has no launchable cmd "
"(adopted from external launch). Use POST /cookbook/serve "
"with the actual cmd instead.")
# Reuse the serve handler we already validated.
from routes.cookbook_helpers import ServeRequest
body = {"repo_id": repo_id, "cmd": cmd}
if host:
body["remote_host"] = host
try:
req = ServeRequest(**body)
except Exception as exc:
raise HTTPException(400, f"Preset payload invalid: {exc}")
serve_endpoint = _find_endpoint(None, "POST", "/api/model/serve")
if serve_endpoint is None:
from fastapi import FastAPI
app: FastAPI = request.app
for route in app.routes:
if getattr(route, "path", None) == "/api/model/serve" and "POST" in getattr(route, "methods", set()):
serve_endpoint = route.endpoint
break
if serve_endpoint is None:
raise HTTPException(503, "model serve endpoint unavailable")
return await serve_endpoint(request, req)
@router.post("/cookbook/adopt")
async def codex_cookbook_adopt(request: Request, body: dict[str, Any] = Body(default_factory=dict)):
"""Adopt an existing tmux session (one started via raw ssh+tmux) into
cookbook tracking. Needed when serve_model rejects a cmd and the
agent falls back to direct ssh — without adoption the session is
invisible to the UI. Body: {tmux_session, model, host?, port?}."""View on GitHub (pinned to f9235ebbf1)
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.
Example fix
// before
# preset stored remote_host = "user@10.0.0.5:22" (validator rejects)
// after
# fix stored preset host to the accepted form, e.g. "user@10.0.0.5"
await post('/cookbook/preset/' + name) Defensive patterns
Strategy: validation
Validate before calling
from routes.cookbook_helpers import ServeRequest
try:
ServeRequest(repo_id=repo_id, cmd=cmd, remote_host=host or None)
except Exception as e:
fix_preset_fields(str(e)) Try / catch
except HTTPException as e:
if e.status_code == 400 and 'Preset payload invalid' in e.detail:
# detail embeds the pydantic error; surface it to the preset editor
show_field_error(e.detail) Prevention
- Re-save presets after upgrading so the current schema revalidates them.
- Run ServeRequest construction client-app-side (mirror of the schema) before launching.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Invalid draft payload: {exc}
- await res.text()
- {key} must be an integer
- Invalid JSON
- Expected a JSON object
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/f2daf32b9eda8a94.
Report an issue: GitHub.