{"record":{"id":"372d659bb712322c","repo":"odysseus-dev/odysseus","slug":"invalid-gpus-expected-comma-separated-gpu-indexe","errorCode":null,"errorMessage":"Invalid gpus — expected comma-separated GPU indexes","messagePattern":"Invalid gpus — expected comma-separated GPU indexes","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"routes/cookbook_helpers.py","lineNumber":136,"sourceCode":"    v = v.rstrip(\"/\") or \"/\"\n    if not (_LOCAL_DIR_RE.match(v) or _WINDOWS_LOCAL_DIR_RE.match(v)):\n        raise HTTPException(400, \"Invalid local_dir — must be an absolute or ~ path with no shell metacharacters\")\n    # Reject path segments that start with '-' (option injection). '-' is in the\n    # allowlist, so a dir like ``/models/-rf`` or ``D:\\models\\-rf`` could be read\n    # as a CLI flag by hf/etc. — and quoting does NOT stop a value from being\n    # parsed as an option. This is the one residual that command-build-time\n    # quoting can't cover, so the guard lives here, keeping the safety wholly\n    # inside the validator rather than relying on consumers.\n    if any(seg.startswith(\"-\") for seg in re.split(r\"[\\\\/]\", v) if seg):\n        raise HTTPException(400, \"Invalid local_dir — path segments cannot start with '-'\")\n    return v\n\n\ndef _validate_gpus(v: str | None) -> str | None:\n    if v is None or v == \"\":\n        return None\n    if not _GPU_LIST_RE.fullmatch(str(v)):\n        raise HTTPException(400, \"Invalid gpus — expected comma-separated GPU indexes\")\n    return str(v)\n\n\ndef _shell_path(p: str) -> str:\n    \"\"\"Render a validated path for a double-quoted shell context, expanding a\n    leading ~ to $HOME (single quotes wouldn't expand it). Safe because\n    _validate_local_dir already rejects quotes and shell metacharacters.\"\"\"\n    if p == \"~\":\n        return '\"$HOME\"'\n    if p.startswith(\"~/\"):\n        return '\"$HOME/' + p[2:] + '\"'\n    return '\"' + p + '\"'\n\n\ndef _local_tooling_path_export(executable: str) -> str:\n    \"\"\"Bash line prepending the running interpreter's bin dir to PATH.\n\n    When Odysseus runs from a virtualenv, that bin dir holds the tools the","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/cookbook_helpers.py#L118-L154","documentation":"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.","triggerScenarios":"gpus=\"0, 1\" (space after comma), gpus=\"GPU0,GPU1\", gpus=\"0-3\" (range syntax), gpus=\"all\", gpus=\"0,1,\" (trailing comma).","commonSituations":"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.","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"],"exampleFix":"// before\n{\"gpus\": \"0-3\"}\n// after\n{\"gpus\": \"0,1,2,3\"}","handlingStrategy":"validation","validationCode":"import re\nGPU_LIST = re.compile(r\"^\\d+(,\\d+)*$\")\n\ndef normalize_gpus(v):\n    if v is None or v == \"\":\n        return None\n    v = str(v).replace(\" \", \"\")\n    if not GPU_LIST.fullmatch(v):\n        raise ValueError(\"gpus must be comma-separated integer indexes\")\n    return v","typeGuard":"const isGpuList = (v: string) => /^\\d+(,\\d+)*$/.test(v.replace(/\\s/g, \"\"));","tryCatchPattern":null,"preventionTips":["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'"],"tags":["validation","gpu","http-400"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}