From 73ed09e145826c3521dc770ffefeb33b3efa1a9f Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Sat, 11 Apr 2026 13:14:40 +0000 Subject: [PATCH] fix(gateway): keep venv python symlink unresolved when remapping paths _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//.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) --- hermes_cli/gateway.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/hermes_cli/gateway.py b/hermes_cli/gateway.py index 6c2b59c96..c049c0f96 100644 --- a/hermes_cli/gateway.py +++ b/hermes_cli/gateway.py @@ -768,14 +768,22 @@ def _remap_path_for_user(path: str, target_home_dir: str) -> str: /root/.hermes/hermes-agent -> /home/alice/.hermes/hermes-agent /opt/hermes -> /opt/hermes (kept as-is) + + Note: this function intentionally does NOT resolve symlinks. A venv's + ``bin/python`` is typically a symlink to the base interpreter (e.g. a + uv-managed CPython at ``~/.local/share/uv/python/.../python3.11``); + resolving that symlink swaps the unit's ``ExecStart`` to a bare Python + that has none of the venv's site-packages, so the service crashes on + the first ``import``. Keep the symlinked path so the venv activates + its own environment. Lexical expansion only via ``expanduser``. """ - current_home = Path.home().resolve() - resolved = Path(path).resolve() + current_home = Path.home() + p = Path(path).expanduser() try: - relative = resolved.relative_to(current_home) + relative = p.relative_to(current_home) return str(Path(target_home_dir) / relative) except ValueError: - return str(resolved) + return str(p) def _hermes_home_for_target_user(target_home_dir: str) -> str: