odysseus-dev/odysseus · error · HTTPException
ssh_port requires host
Error message
ssh_port requires host
What it means
Raised by _validate_detection_target in the hardware-fit routes when a caller supplies ssh_port without host. The validator normalizes host via validate_remote_host and port via validate_ssh_port; a port only makes sense as part of a host:port SSH target, so a bare port is rejected with 400 before any fitting logic runs.
Source
Thrown at routes/hwfit_routes.py:25
from fastapi import APIRouter, HTTPException
from core.platform_compat import run_ssh_command
from routes._validators import validate_remote_host, validate_ssh_port
# Backends the manual hardware simulator accepts. Must stay a subset of what
# services.hwfit.fit understands so a simulated box ranks like a real one:
# "metal" routes through the Apple-Silicon path (GGUF-only, llama.cpp/Ollama),
# the CPU backends through the RAM/offload path, cuda/rocm through vLLM.
_MANUAL_BACKENDS = {"cuda", "rocm", "metal", "cpu_x86", "cpu_arm"}
def _validate_detection_target(host: str = "", ssh_port: str = "") -> tuple[str, str]:
host_value = validate_remote_host(host) or ""
port_value = validate_ssh_port(ssh_port) or ""
if port_value and not host_value:
raise HTTPException(400, "ssh_port requires host")
return host_value, port_value
def _apply_manual_hardware(system, manual_mode="", manual_gpu_count="", manual_vram_gb="", manual_ram_gb="", manual_backend=""):
"""Manual hardware is a "what if I had this setup" simulator —
REPLACES the detected hardware entirely instead of adding to it.
The previous additive behavior averaged the manual VRAM across
all GPUs (base + manual), which meant adding "1× 400 GB" on top
of "2× 70 GB" only nudged the per-GPU cap from 70 to 180 GB
(= 540 / 3), so GGUF models bigger than that still didn't surface
— exactly the "cap stuck at detected level" bug the user hit.
"""
manual_mode = (manual_mode or "").lower()
if manual_mode not in {"gpu", "ram"}:
return system
try:View on GitHub (pinned to f9235ebbf1)
Solutions
- Include the host field whenever ssh_port is sent (e.g. host="192.168.1.10" plus ssh_port="22").
- If you do not want remote detection, omit ssh_port entirely and run local detection.
- Check the client payload right before submit and log it — verify host is non-empty after your form-binding step, since validate_remote_host returning empty is what trips the check.
Example fix
# before
payload = {"ssh_port": "22"}
# after
payload = {"host": "192.168.1.10", "ssh_port": "22"} Defensive patterns
Strategy: validation
Validate before calling
def hwfit_params_valid(host: str, ssh_port: str) -> bool:
return bool(ssh_port.strip()) <= bool(host.strip()) # port implies host Try / catch
Catch HTTPException/400 responses from the hwfit endpoint and re-prompt for the host field; do not retry unchanged.
Prevention
- Make the host field required in the UI whenever ssh_port is non-empty.
- Log the exact payload on 400 to catch form-binding mistakes early.
When it happens
Trigger: Calling a /hwfit detection/fit endpoint with ssh_port="22" (or any port) in the form/query while the host field is empty, or a UI that remembers the port but not the hostname from a previous submission.
Common situations: Frontend form where the host input failed to bind or was cleared; scripts that template the port but leave host blank; copy-pasting a config where host key name differs (e.g. hostname vs host).
Related errors
- Invalid remote_host — must be host or user@host, no SSH opti
- Invalid ssh_port
- Invalid SSH remote host
- Unknown wipe kind: {kind!r}
- Username required
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/c91dbfcbdbb48a10.
Report an issue: GitHub.