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

  1. Read the {exc} suffix — it names the failing field; fix that field in the preset (via presets API or cookbook_state.json).
  2. Re-save the preset through POST /cookbook/serve so it is validated by the current ServeRequest schema.
  3. 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

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


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/f2daf32b9eda8a94. Report an issue: GitHub.