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 the

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Send only digits separated by commas with no spaces: "0,1,2"
  2. Expand ranges yourself: "0-3" becomes "0,1,2,3"
  3. Omit gpus entirely when default device selection is fine
  4. 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

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.