System service path mismatch when installed as root
NousResearch/hermes-agent · cluster 6989 · hermes cli
Data as of
Nothing open on GitHub. Fix merged in #7109. Check it covers your case.
The short answer
0open issues
0open PRs
6items grouped
- Known?
- Yes. 6 related items grouped here, 2 issues already closed.
- Fix event
- A fix was merged to main in PR #7109. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
partially resolved, deciding PR #7109. That is separate from the fix event. - Still open
- Nothing open on GitHub.
A bug where the gateway system service unit uses incorrect paths when installed as root, and related fixes to remap Python, venv, and working directory paths to the target user's home directory.
Open issues0
Open PRs0
Fixes on record
-
#7109 fix(gateway): remap all paths in system service unit to target user's home
files:
hermes_cli/gateway.py,tests/hermes_cli/test_gateway_service.pyShow the diff
-
hermes_cli/gateway.py@@ -618,6 +618,24 @@ def _build_user_local_paths(home: Path, path_entries: list[str]) -> list[str]: return [p for p in candidates if p not in path_entries and Path(p).exists()] +def _remap_path_for_user(path: str, target_home_dir: str) -> str: + """Remap *path* from the current user's home to *target_home_dir*. + + If *path* lives under ``Path.home()`` the corresponding prefix is swapped + to *target_home_dir*; otherwise the path is returned unchanged. + + /root/.hermes/hermes-agent -> /home/alice/.hermes/hermes-agent + /opt/hermes -> /opt/hermes (kept as-is) + """ + current_home = Path.home().resolve() + resolved = Path(path).resolve() + try: + relative = resolved.relative_to(current_home) + return str(Path(target_home_dir) / relative) + except ValueError: + return str(resolved) + + def _hermes_home_for_target_user(target_home_dir: str) -> str: """Remap the current HERMES_HOME to the equivalent under a target user's home. @@ -665,6 +683,15 @@ def generate_systemd_unit(system: bool = False, run_as_user: str | None = None) username, group_name, home_dir = _system_service_identity(run_as_user) hermes_home = _hermes_home_for_target_user(home_dir) profile_arg = _profile_arg(hermes_home) + # Remap all paths that may resolve under the calling user's home + # (e.g. /root/) to the target user's home so the service can + # actually access them. + python_path = _remap_path_for_user(python_path, home_dir) + working_dir = _remap_path_for_user(working_dir, home_dir) + venv_dir = _remap_path_for_user(venv_dir, home_dir) + venv_bin = _remap_path_for_user(venv_bin, home_dir) + node_bin = _remap_path_for_user(node_bin, home_dir) + path_entries = [_remap_path_for_user(p, home_dir) for p in path_entries] path_entries.extend(_build_user_local_paths(Path(home_dir), path_entries)) path_entries.extend(common_bin_paths) sane_path = ":".join(path_entries) -
tests/hermes_cli/test_gateway_service.py@@ -754,3 +754,63 @@ def test_launchd_plist_includes_profile(self, tmp_path, monkeypatch): plist = gateway_cli.generate_launchd_plist() assert "<string>--profile</string>" in plist assert "<string>mybot</string>" in plist + + +class TestRemapPathForUser: + """Unit tests for _remap_path_for_user().""" + + def test_remaps_path_under_current_home(self, monkeypatch, tmp_path): + monkeypatch.setattr(Path, "home", lambda: tmp_path / "root") + (tmp_path / "root").mkdir() + result = gateway_cli._remap_path_for_user( + str(tmp_path / "root" / ".hermes" / "hermes-agent"), + str(tmp_path / "alice"), + ) + assert result == str(tmp_path / "alice" / ".hermes" / "hermes-agent") + + def test_keeps_system_path_unchanged(self, monkeypatch, tmp_path): + monkeypatch.setattr(Path, "home", lambda: tmp_path / "root") + (tmp_path / "root").mkdir() + result = gateway_cli._remap_path_for_user("/opt/hermes", str(tmp_path / "alice")) + assert result == "/opt/hermes" + + def test_noop_when_same_user(self, monkeypatch, tmp_path): + monkeypatch.setattr(Path, "home", lambda: tmp_path / "alice") + (tmp_path / "alice").mkdir() + original = str(tmp_path / "alice" / ".hermes" / "hermes-agent") + result = gateway_cli._remap_path_for_user(original, str(tmp_path / "alice")) + assert result == original + + +class TestSystemUnitPathRemapping: + """System units must remap ALL paths from the caller's home to the target user.""" + + def test_system_unit_has_no_root_paths(self, monkeypatch, tmp_path): + root_home = tmp_path / "root" + root_home.mkdir() + project = root_home / ".hermes" / "hermes-agent" + project.mkdir(parents=True) + venv_bin = project / "venv" / "bin" + venv_bin.mkdir(parents=True) + (venv_bin / "python").write_text("") + + target_home = "/home/alice" + + monkeypatch.setattr(Path, "home", lambda: root_home) + monkeypatch.setenv("HERMES_HOME", str(root_home / ".hermes")) + monkeypatch.setattr(gateway_cli, "get_hermes_home", lambda: root_home / ".hermes") + monkeypatch.setattr(gateway_cli, "PROJECT_ROOT", project) + monkeypatch.setattr(gateway_cli, "_detect_venv_dir", lambda: project / "venv") + monkeypatch.setattr(gateway_cli, "get_python_path", lambda: str(venv_bin / "python")) + monkeypatch.setattr( + gateway_cli, "_system_service_identity", + lambda run_as_user=None: ("alice", "alice", target_home), + ) + + unit = gateway_cli.generate_systemd_unit(system=True) + + # No root paths should leak into the unit + assert str(root_home) not in unit + # Target user paths should be present + assert "/home/alice" in unit + assert "WorkingDirectory=/home/alice/.hermes/hermes-agent" in unit
-
3 PRs closed without merging
From the close comments: merged via another change 2 · done elsewhere 1
-
#7016 fix(gateway): remap python/venv/workdir to target user when installing system service as root · merged via another change →
#7109 -
#7030 fix(gateway): remap all paths in system service unit to target user's home · merged via another change →
#7109 -
#25298 fix(cli): preserve systemd unit paths when service user differs from CLI user · done elsewhere →
a1cb5fa
Closed issues · 2
How this cluster was grouped
-
Link evidence (
partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text. - Members outside that structural core may belong to a different underlying problem.
-
Pairwise link density
0.29: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the LabAt a glance
- Subsystem
- hermes cli
- Merged PRs
- #7109
- Closed issues
- 2
- Attempts
- 3 PRs closed without merging
- Fix link checked
- 10 Apr 2026. This confirmation is old and may be out of date (a revert or reopen would not have invalidated it here); re-verify.