{"record":{"id":"0cc634ac6a15b689","repo":"odysseus-dev/odysseus","slug":"invalid-env-prefix","errorCode":null,"errorMessage":"Invalid env_prefix","messagePattern":"Invalid env_prefix","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"routes/cookbook_helpers.py","lineNumber":1167,"sourceCode":"    \"\"\"Build SSH command string with optional port.\"\"\"\n    pf = f\"-p {port} \" if port and port != \"22\" else \"\"\n    return f\"ssh {pf}{host} '{cmd}'\"\n\n\ndef _safe_env_prefix(ep: str | None) -> str | None:\n    \"\"\"Rewrite a `source <path>` env_prefix so it no-ops if the path is missing.\n    Prevents `line N: <path>: No such file or directory` errors when a serve\n    task is launched against a host that doesn't have the expected venv.\n\n    Also rewrites leading `~/` → `$HOME/` so the path expands inside double\n    quotes (bash only tilde-expands unquoted tokens at word start).\"\"\"\n    if not ep:\n        return ep\n    import shlex\n    try:\n        parts = shlex.split(ep, posix=True)\n    except ValueError:\n        raise HTTPException(400, \"Invalid env_prefix\")\n    if len(parts) != 2 or parts[0] not in {\"source\", \".\"}:\n        # Bash conda activation emitted by the frontend:\n        #   eval \"$(conda shell.bash hook)\" && conda activate ENV\n        m = re.fullmatch(r'eval \"\\$\\(conda shell\\.bash hook\\)\" && conda activate (.+)', ep)\n        if m:\n            env = m.group(1).strip()\n            try:\n                env_parts = shlex.split(env, posix=True)\n            except ValueError:\n                raise HTTPException(400, \"Invalid env_prefix\")\n            if len(env_parts) != 1:\n                raise HTTPException(400, \"Invalid env_prefix\")\n            return 'eval \"$(conda shell.bash hook)\" && conda activate ' + shlex.quote(env_parts[0])\n\n        # Plain conda activation, used by Windows/PowerShell and some manual callers.\n        if len(parts) == 3 and parts[0] == \"conda\" and parts[1] == \"activate\":\n            return \"conda activate \" + shlex.quote(parts[2])\n","sourceCodeStart":1149,"sourceCodeEnd":1185,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/cookbook_helpers.py#L1149-L1185","documentation":"HTTP 400 from the env_prefix normalizer in routes/cookbook_helpers.py (~line 1167) when shlex.split(ep, posix=True) raises ValueError — the env_prefix has unbalanced quotes or an unterminated escape, so it cannot be tokenized into a source-command form.","triggerScenarios":"env_prefix=\"source '/opt/venv with space/bin/activate\" (opening quote never closed), or a prefix ending in a bare backslash.","commonSituations":"Paths with spaces quoted on only one side; copy-pasting a Windows path with a stray quote; frontend assembling the string with a broken template that drops the closing quote.","solutions":["Balance the quotes: source \\\"/opt/my venv/bin/activate\\\"","Avoid backslashes except as proper escapes; for Windows paths prefer forward slashes","Generate the string with shlex.quote() so quoting is always well-formed","Test with python -c 'import shlex; shlex.split(your_string)' before submitting"],"exampleFix":"# before\nenv_prefix = \"source '/opt/my venv/bin/activate\"\n# after\nenv_prefix = 'source \"/opt/my venv/bin/activate\"'","handlingStrategy":"validation","validationCode":"import shlex\n\ndef env_prefix_parses(ep: str) -> bool:\n    try:\n        shlex.split(ep, posix=True)\n        return True\n    except ValueError:\n        return False\n\nassert env_prefix_parses(env_prefix), \"unbalanced quotes in env_prefix\"","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Build env_prefix with shlex.quote() so quoting stays balanced","Quote paths containing spaces on both sides","Prefer standard venv locations without spaces"],"tags":["validation","shell-quoting","env-prefix","http-400"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}