Skip to content

fix(gateway): fall back to python3 when venv lacks bare python symlink - #7985

Closed
hugobiais wants to merge 1 commit into
NousResearch:mainfrom
hugobiais:fix/systemd-execstart-venv-python
Closed

fix(gateway): fall back to python3 when venv lacks bare python symlink#7985
hugobiais wants to merge 1 commit into
NousResearch:mainfrom
hugobiais:fix/systemd-execstart-venv-python

Conversation

@hugobiais

Copy link
Copy Markdown

Summary

Fixes #7976.

hermes gateway install --system generates a unit file whose ExecStart points at the base uv-managed Python interpreter instead of the project venv. The service starts, cannot import yaml (or any other Hermes dependency installed into the venv's site-packages), and crash-loops.

Root cause is in get_python_path() in hermes_cli/gateway.py:

def get_python_path() -> str:
    venv = _detect_venv_dir()
    if venv is not None:
        if is_windows():
            venv_python = venv / "Scripts" / "python.exe"
        else:
            venv_python = venv / "bin" / "python"   # ← only checks bare "python"
        if venv_python.exists():
            return str(venv_python)
    return sys.executable                            # ← falls through to base interpreter

The venv is detected correctly via sys.prefix, but the helper only probes venv/bin/python. On uv-managed installs where the bare python symlink is absent (and only python3 / python3.11 exist inside the venv's bin/), the .exists() check fails and the function falls through to sys.executable, which on such installs resolves to the base interpreter under ~/.local/share/uv/python/.... generate_systemd_unit() then bakes that base interpreter into ExecStart — while still (correctly) setting VIRTUAL_ENV= and PATH= to the venv — so the running service has no access to the venv's site-packages and any import yaml (and similar) blows up on startup.

Fix

Try python, then python3, then pythonX.Y inside the detected venv before giving up on it. python is still checked first, so installs where the bare symlink exists are unaffected.

major_minor = f"python{sys.version_info.major}.{sys.version_info.minor}"
candidates = [
    venv / "bin" / "python",
    venv / "bin" / "python3",
    venv / "bin" / major_minor,
]
for candidate in candidates:
    if candidate.exists():
        return str(candidate)

The change is scoped to get_python_path()generate_systemd_unit() itself is untouched, and the _remap_path_for_user() pipeline that rewrites /root/... into the target user's home for --system installs still works unchanged since python_path is assigned through the same helper before remapping.

Tests

Added TestGetPythonPath in tests/hermes_cli/test_gateway_service.py covering:

  • test_prefers_bare_python_when_available — existing behavior preserved when the bare python symlink is present.
  • test_falls_back_to_python3_when_bare_python_missing — the regression scenario: only python3 / python3.11 in the venv → returns venv/bin/python3.
  • test_falls_back_to_versioned_python — only the versioned pythonX.Y exists → returns that.
  • test_returns_sys_executable_when_venv_has_no_interpreter — empty venv bin/ → falls back to sys.executable (unchanged).
  • test_returns_sys_executable_when_no_venv_detected_detect_venv_dir() returns None → falls back to sys.executable (unchanged).
$ pytest tests/hermes_cli/test_gateway_service.py -v
...
============================== 67 passed in 1.84s ==============================

All 67 tests in test_gateway_service.py pass (62 pre-existing + 5 new), no other files touched.

Reproduction (for reference)

Host: Ubuntu 24.04, Hermes Agent v0.8.0, standard installer, running as root on a VPS.

$ hermes gateway install --system --run-as-user root
$ hermes gateway start --system
$ journalctl -u hermes-gateway -f
...
python3.11[45120]: ModuleNotFoundError: No module named 'yaml'
systemd[1]: hermes-gateway.service: Main process exited, code=exited, status=1/FAILURE

Generated unit (pre-fix):

ExecStart=/root/.local/share/uv/python/cpython-3.11.15-linux-x86_64-gnu/bin/python3.11 -m hermes_cli.main gateway run --replace
Environment="VIRTUAL_ENV=/root/.hermes/hermes-agent/venv"
Environment="PATH=/root/.hermes/hermes-agent/venv/bin:..."

The meanwhile-workaround (systemd drop-in override that hardcodes the venv python) is documented in #7976.

`get_python_path()` only checked `venv/bin/python`, so if that symlink was
missing the helper fell through to `sys.executable`, which on a uv-managed
install resolves to the base interpreter. `generate_systemd_unit()` then
baked the base interpreter into `ExecStart`, while still setting
`VIRTUAL_ENV=` and `PATH=` to the venv — causing the service to start
without the venv's `site-packages` and crash with `ModuleNotFoundError`
for any dependency installed into the venv (e.g. `yaml`).

Try `python`, then `python3`, then `pythonX.Y` inside the venv before
giving up on it, so the generated unit uses an interpreter that can
actually import the Hermes dependencies.

Fixes NousResearch#7976
@teknium1

Copy link
Copy Markdown
Contributor

Already fixed on main via PR #8861 (salvage of #7735 by @akhater). The current code drops .resolve() entirely and uses .expanduser() for lexical-only expansion, preserving venv symlinks. Thanks for the contribution!

@teknium1 teknium1 closed this Apr 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: gateway install --system ExecStart skips project venv

2 participants