fix(gateway): use PROJECT_ROOT/venv/bin/python as fallback instead of sys.executable

## What broke
After `hermes gateway install --system`, the systemd service ExecStart
uses the uv-managed Python path instead of the venv Python path. This
causes immediate crash with `ModuleNotFoundError: No module named 'yaml'`
because the uv Python has no packages installed.

ExecStart=/root/.local/share/uv/python/cpython-3.11.15-linux-x86_64-gnu/bin/python3.11
Should be:
ExecStart=/root/.hermes/hermes-agent/venv/bin/python3.11

## Root cause
`get_python_path()` in hermes_cli/gateway.py fell back to `sys.executable`
when `_detect_venv_dir()` returned None. In uv-managed environments,
`sys.executable` returns the uv Python path which has no packages.

Meanwhile, `generate_systemd_unit()` already uses `PROJECT_ROOT/venv` as
fallback for `venv_dir` (line 819), creating an inconsistency:
- `venv_dir` = PROJECT_ROOT/venv (correct)
- `python_path` = sys.executable (wrong - uv path)

## Why this fix is minimal
Added 11 lines to `get_python_path()` to add a middle fallback tier:
1. detected_venv/bin/python (existing - highest priority)
2. PROJECT_ROOT/venv/bin/python (NEW - matches generate_systemd_unit)
3. sys.executable (existing - final fallback)

This makes `get_python_path()` consistent with `venv_dir` calculation
in `generate_systemd_unit()`. No behavior change when venv is detected.

## What I tested
Added test suite tests/hermes_cli/test_gateway_python_path.py with 8 tests:
- test_fallback_to_project_root_venv_when_detect_fails
- test_detect_venv_dir_returns_valid_venv
- test_fallback_chain_order
- test_consistency_with_venv_dir_in_systemd_unit
- test_windows_fallback_uses_scripts_python_exe
- test_execstart_not_uv_python_path
- test_system_unit_execstart_uses_venv_python
- test_user_unit_execstart_uses_venv_python

All verify ExecStart uses venv/bin/python, not uv Python path.

## What I intentionally did not change
- No changes to _detect_venv_dir() logic
- No changes to generate_systemd_unit() venv_dir calculation
- No opportunistic refactoring
- No changes to launchd plist generation (similar pattern)

Fixes #9201
This commit is contained in:
linux2010 2026-04-14 11:46:21 +00:00
parent 16f9d02084
commit a844ff0ddc
2 changed files with 163 additions and 0 deletions

View file

@ -742,6 +742,17 @@ def get_python_path() -> str:
venv_python = venv / "bin" / "python"
if venv_python.exists():
return str(venv_python)
# Fallback: use PROJECT_ROOT/venv/bin/python (same logic as generate_systemd_unit)
# This is more reliable than sys.executable when running under uv.
fallback_venv = PROJECT_ROOT / "venv"
if is_windows():
fallback_python = fallback_venv / "Scripts" / "python.exe"
else:
fallback_python = fallback_venv / "bin" / "python"
if fallback_python.exists():
return str(fallback_python)
return sys.executable