Skip to content

fix(gateway): keep venv python symlink unresolved when remapping paths for systemd unit - #7735

Closed
akhater wants to merge 1 commit into
NousResearch:mainfrom
akhater:fix/gateway-install-venv-symlink-resolve
Closed

fix(gateway): keep venv python symlink unresolved when remapping paths for systemd unit#7735
akhater wants to merge 1 commit into
NousResearch:mainfrom
akhater:fix/gateway-install-venv-symlink-resolve

Conversation

@akhater

@akhater akhater commented Apr 11, 2026

Copy link
Copy Markdown
Contributor

Summary

hermes gateway install --system writes a systemd unit whose ExecStart= points at the resolved target of venv/bin/python instead of the symlink itself. On uv-managed venvs (where venv/bin/python is a symlink into ~/.local/share/uv/python/...) this swaps the unit's Python to the bare base interpreter, which has none of the venv's site-packages. The service crashes on its first import yaml and loops forever on Restart=on-failure.

Classical python -m venv installs were unaffected by accident — the venv's bin/python symlinks to /usr/bin/python3.x, which is outside $HOME, so the relative_to(Path.home()) path was skipped and the system Python (which has yaml installed globally) silently worked.

Repro

On a uv-managed venv (i.e. ~/.hermes/hermes-agent/venv/bin/python is a symlink into ~/.local/share/uv/python/cpython-X.Y.Z-linux-x86_64-gnu/bin/python3.x):

sudo -E hermes -p <profile> gateway install --system --force
grep ExecStart /etc/systemd/system/hermes-gateway-<profile>.service
# ExecStart=/home/<user>/.local/share/uv/python/cpython-3.11.15-linux-x86_64-gnu/bin/python3.11 \
#     -m hermes_cli.main --profile <profile> gateway run --replace
sudo systemctl start hermes-gateway-<profile>
journalctl -u hermes-gateway-<profile> -n 20 --no-pager

Observed:

python3.11[XXXX]: ModuleNotFoundError: No module named 'yaml'
systemd[1]: hermes-gateway-<profile>.service: Main process exited, code=exited, status=1/FAILURE
systemd[1]: hermes-gateway-<profile>.service: Failed with result 'exit-code'.
systemd[1]: hermes-gateway-<profile>.service: Scheduled restart job, restart counter is at 1.

The hermes gateway start --system wrapper prints ✓ System service started because systemctl start returned 0 — systemd accepted the start request. It does not verify the process survived past the first second.

Root cause

hermes_cli/gateway.py:_remap_path_for_user was doing:

current_home = Path.home().resolve()
resolved = Path(path).resolve()   # <-- follows symlinks all the way

The function's documented purpose is lexical prefix substitution:

/root/.hermes/hermes-agent → /home/alice/.hermes/hermes-agent

i.e. remapping /root/.hermes to /home/<user>/.hermes when installing a system service as root for a target user. Symlink resolution is an unrelated operation that happens to work for classical venvs (because /usr/bin/python3.x escapes $HOME and falls through the except ValueError branch to return the system Python) and silently breaks uv-managed venvs (where the resolved target is still under $HOME, gets the home-prefix remap applied, and lands on a bare Python outside the venv's site-packages).

Fix

Drop the .resolve() calls. Use .expanduser() for lexical ~ expansion only.

def _remap_path_for_user(path: str, target_home_dir: str) -> str:
    current_home = Path.home()
    p = Path(path).expanduser()
    try:
        relative = p.relative_to(current_home)
        return str(Path(target_home_dir) / relative)
    except ValueError:
        return str(p)

Backward compatibility:

  • Classical venv: still works. venv/bin/python typically lives under $HOME/<project>/venv/bin/python, which is already under Path.home() — the relative_to branch was taken before and is still taken now, just without following symlinks. Either way, invoking venv/bin/python activates the venv's site-packages.
  • Cross-user system install (root installing for user alice): still works. The lexical prefix swap is the entire point of the function and is preserved.
  • Non-home paths (e.g. /opt/hermes): still returned unchanged — the relative_to call throws ValueError and the fallback branch returns the unmodified path.

Test plan

  • uv-managed venv: install + start, assert systemctl is-active reports active, no ModuleNotFoundError in journalctl
  • classical venv: same sequence, assert still working (non-regression)
  • cross-user install as root for target user: assert ExecStart path has /home/<target> prefix, unit file is in /etc/systemd/system/, start succeeds
  • /opt/hermes-style install (path outside $HOME): assert path is returned unchanged

_remap_path_for_user was calling .resolve() on the Python path, which
followed venv/bin/python into the base interpreter. On uv-managed venvs
this swaps the systemd ExecStart to a bare Python that has none of the
venv's site-packages, so the service crashes on first import. Classical
python -m venv installs were unaffected by accident: the resolved target
/usr/bin/python3.x lives outside $HOME so the path-remap branch was
skipped and the system Python's packages silently worked.

Remove .resolve() calls on both current_home and the path; use
.expanduser() for lexical tilde expansion only. The function does
lexical prefix substitution, which is all it needs to do for its
actual purpose (remapping /root/.hermes -> /home/<user>/.hermes when
installing system services as root for a different user).

Repro: on a uv-managed venv install, `sudo hermes gateway install
--system` writes ExecStart=.../uv/python/cpython-3.11.15-.../bin/python3.11
instead of .../hermes-agent/venv/bin/python, and the service crashes on
ModuleNotFoundError: yaml.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@teknium1

Copy link
Copy Markdown
Contributor

Merged via PR #8861. Your commit was cherry-picked onto current main with your authorship preserved in git log. Thanks for the fix and the thorough analysis, @akhater!

@teknium1 teknium1 closed this Apr 13, 2026
@akhater

akhater commented Apr 13, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @teknium1!

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.

2 participants