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

  1. Send a valid host (or user@host) in the SetupRequest body, e.g. {"host": "user@10.0.0.5", "ssh_port": "22"}.
  2. Add a client-side required-field check on the host input before submitting the setup form.
  3. 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

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


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