odysseus-dev/odysseus · error · HTTPException
host is required
Error message
host is required
What it means
Raised (HTTP 400) by POST /api/cookbook/setup when the SetupRequest body's host field normalizes to empty. validate_remote_host() returns None for empty/None input (it only raises for malformed hosts), and the route then requires a non-empty host because dependency installation only works over SSH against a named remote server. Admin-gated via require_admin.
Source
Thrown at routes/cookbook_routes.py:2843
except Exception:
pass
return {"ok": True, "session_id": session_id, "remote": remote or "local",
"endpoint_id": endpoint_id}
# ── Server setup (install deps on remote) ──
class SetupRequest(BaseModel):
host: str
ssh_port: str | None = None
@router.post("/api/cookbook/setup")
async def server_setup(request: Request, req: SetupRequest):
"""Install required dependencies on a remote server via SSH."""
require_admin(request)
host = validate_remote_host(req.host)
if not host:
raise HTTPException(400, "host is required")
port = req.ssh_port
port = validate_ssh_port(port)
pf = f"-p {port} " if port and port != "22" else ""
# Detect platform: Windows first (echo %OS% → Windows_NT), then Termux, then Linux
detect_cmd = f'ssh {pf}{host} "echo %OS%"'
platform = "linux"
try:
proc = await asyncio.create_subprocess_shell(
detect_cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
)
stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=10)
out = stdout.decode().strip()
if "Windows_NT" in out:
platform = "windows"
else:
# Check for Termux
detect_cmd2 = f"ssh {pf}{host} 'test -d /data/data/com.termux && echo termux || echo linux'"View on GitHub (pinned to f9235ebbf1)
Solutions
- Send a valid host (or user@host) in the SetupRequest body, e.g. {"host": "user@10.0.0.5", "ssh_port": "22"}.
- Add a client-side required-field check on the host input before submitting the setup form.
- Note the accepted format: validate_remote_host only allows 'host' or 'user@host' with no spaces or SSH option syntax — values like 'ssh -i key host' get a different 400 ('Invalid remote_host').
Example fix
// before
await post('/api/cookbook/setup', { host: '', ssh_port: '22' }); // 400 host is required
// after
await post('/api/cookbook/setup', { host: 'user@gpu-box', ssh_port: '22' }); Defensive patterns
Strategy: validation
Validate before calling
const HOST_RE = /^(?:[A-Za-z0-9][A-Za-z0-9._-]*@)?[A-Za-z0-9][A-Za-z0-9._-]*$/;
function validateSetupBody(body) {
if (!body?.host || !HOST_RE.test(body.host)) throw new Error('host is required and must be host or user@host');
return body;
} Type guard
function isSetupRequest(b: unknown): b is { host: string; ssh_port?: string } {
return typeof b === 'object' && b !== null && typeof (b as any).host === 'string' && (b as any).host.length > 0;
} Try / catch
try { await post('/api/cookbook/setup', body); } catch (e) { if (e.status === 400 && e.detail === 'host is required') { focusHostInput(); return; } throw e; } Prevention
- Make the host input required in the form UI before enabling submit.
- Reuse the same host regex client-side as routes/_validators.py to catch both empty and malformed hosts early.
When it happens
Trigger: POST /api/cookbook/setup with host omitted, host: "", or host: null in the JSON body. Pydantic's SetupRequest declares host: str (required), so omitting it entirely fails Pydantic validation with 422 instead; this 400 appears when host is present but empty string.
Common situations: Frontend sends an empty host field because the user never filled in the remote-server input; a form-reset bug leaves host=''; whitespace-only host slips past client-side checks.
Related errors
- data.detail || data.error || `HTTP ${res.status}`
- data.error || 'Failed to generate SSH key'
- HTTP ${res.status}
- Invalid SSH remote host
- Invalid remote_host — must be host or user@host, no SSH opti
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/30402bb02d18e4f2.
Report an issue: GitHub.