odysseus-dev/odysseus · warning · HTTPException
model required
Error message
model required
What it means
Raised by POST /api/codex/cookbook/adopt when the model field (alias repo_id) is missing or blank after stripping. The model identifier is required so the adopted session can be tracked against a repo/model in cookbook state.
Source
Thrown at routes/codex_routes.py:841
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?}."""
_require_cookbook_scope(request, COOKBOOK_LAUNCH_SCOPES)
norm = dict(body or {})
sess = (norm.get("tmux_session") or norm.get("session_id") or "").strip()
model = (norm.get("model") or norm.get("repo_id") or "").strip()
host = validate_remote_host((norm.get("host") or norm.get("remote_host") or "").strip() or None) or ""
port = norm.get("port") or 8000
import re as _re
if not sess or not _re.fullmatch(r"[a-zA-Z0-9_-]+", sess):
raise HTTPException(400, "tmux_session required, [a-zA-Z0-9_-]+ only")
if not model:
raise HTTPException(400, "model required")
# Verify the tmux session exists on the target host before adopting.
import shlex
if host:
check = f"ssh {shlex.quote(host)} 'tmux has-session -t {shlex.quote(sess)}'"
else:
check = f"tmux has-session -t {shlex.quote(sess)}"
chk = await _run_shell(check, timeout=8)
if chk.get("exit_code") not in (0, None):
raise HTTPException(404, f"tmux session {sess!r} not found on {host or 'local'}")
# Write into cookbook_state.json.
import time as _t, json as _json
from core.atomic_io import atomic_write_json
from pathlib import Path as _Path
cookbook_state_path = _Path(COOKBOOK_STATE_FILE)
try:
state = _json.loads(cookbook_state_path.read_text(encoding="utf-8"))
except Exception:
state = {}View on GitHub (pinned to f9235ebbf1)
Solutions
- Include "model" (or "repo_id") in the adopt body, e.g. "Qwen/Qwen2.5-7B".
- Upgrade the calling client to the current body schema {tmux_session, model, host?, port?}.
Example fix
// before
{"tmux_session":"vllm-1"}
// after
{"tmux_session":"vllm-1","model":"Qwen/Qwen2.5-7B-Instruct"} Defensive patterns
Strategy: validation
Validate before calling
body = {'tmux_session': sess, 'model': model}
assert body['model'] and body['model'].strip() Type guard
const isAdoptBody = (b: Record<string,unknown>) => typeof b.tmux_session === 'string' && typeof (b.model ?? b.repo_id) === 'string' && !!(b.model ?? b.repo_id)
Prevention
- Build adopt payloads from one typed helper shared across callers.
When it happens
Trigger: POST /cookbook/adopt with neither 'model' nor 'repo_id' in the body, or a whitespace-only value.
Common situations: Client sends only tmux_session; field named differently (e.g. 'model_name') in an older client; JSON body omitted the key.
Related errors
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/710a1b043db7b3e6.
Report an issue: GitHub.