odysseus-dev/odysseus · error · HTTPException
Invalid gpus — expected comma-separated GPU indexes
Error message
Invalid gpus — expected comma-separated GPU indexes
What it means
HTTP 400 from _validate_gpus() in routes/cookbook_helpers.py when a non-empty gpus value fails _GPU_LIST_RE.fullmatch. Only a plain comma-separated list of GPU indexes (digits) is accepted — the value is injected into commands like CUDA_VISIBLE_DEVICES, so decorations, ranges, spaces, or identifiers are rejected. Empty/None returns None.
Source
Thrown at routes/cookbook_helpers.py:136
v = v.rstrip("/") or "/"
if not (_LOCAL_DIR_RE.match(v) or _WINDOWS_LOCAL_DIR_RE.match(v)):
raise HTTPException(400, "Invalid local_dir — must be an absolute or ~ path with no shell metacharacters")
# Reject path segments that start with '-' (option injection). '-' is in the
# allowlist, so a dir like ``/models/-rf`` or ``D:\models\-rf`` could be read
# as a CLI flag by hf/etc. — and quoting does NOT stop a value from being
# parsed as an option. This is the one residual that command-build-time
# quoting can't cover, so the guard lives here, keeping the safety wholly
# inside the validator rather than relying on consumers.
if any(seg.startswith("-") for seg in re.split(r"[\\/]", v) if seg):
raise HTTPException(400, "Invalid local_dir — path segments cannot start with '-'")
return v
def _validate_gpus(v: str | None) -> str | None:
if v is None or v == "":
return None
if not _GPU_LIST_RE.fullmatch(str(v)):
raise HTTPException(400, "Invalid gpus — expected comma-separated GPU indexes")
return str(v)
def _shell_path(p: str) -> str:
"""Render a validated path for a double-quoted shell context, expanding a
leading ~ to $HOME (single quotes wouldn't expand it). Safe because
_validate_local_dir already rejects quotes and shell metacharacters."""
if p == "~":
return '"$HOME"'
if p.startswith("~/"):
return '"$HOME/' + p[2:] + '"'
return '"' + p + '"'
def _local_tooling_path_export(executable: str) -> str:
"""Bash line prepending the running interpreter's bin dir to PATH.
When Odysseus runs from a virtualenv, that bin dir holds the tools theView on GitHub (pinned to f9235ebbf1)
Solutions
- Send only digits separated by commas with no spaces: "0,1,2"
- Expand ranges yourself: "0-3" becomes "0,1,2,3"
- Omit gpus entirely when default device selection is fine
- Check _GPU_LIST_RE in routes/cookbook_helpers.py for the exact grammar
Example fix
// before
{"gpus": "0-3"}
// after
{"gpus": "0,1,2,3"} Defensive patterns
Strategy: validation
Validate before calling
import re
GPU_LIST = re.compile(r"^\d+(,\d+)*$")
def normalize_gpus(v):
if v is None or v == "":
return None
v = str(v).replace(" ", "")
if not GPU_LIST.fullmatch(v):
raise ValueError("gpus must be comma-separated integer indexes")
return v Type guard
const isGpuList = (v: string) => /^\d+(,\d+)*$/.test(v.replace(/\s/g, ""));
Prevention
- Join GPU selections without spaces: [0,1].join(',')
- Expand ranges (0-3) to explicit lists before sending
- Omit gpus rather than sending 'all' or 'GPU0'
When it happens
Trigger: gpus="0, 1" (space after comma), gpus="GPU0,GPU1", gpus="0-3" (range syntax), gpus="all", gpus="0,1," (trailing comma).
Common situations: Users copying nvidia-smi or CUDA range syntax (0-3) which this validator deliberately does not support; frontends inserting spaces when joining a multi-select; sending "all" expecting a wildcard.
Related errors
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/372d659bb712322c.
Report an issue: GitHub.