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

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #7109 fix(gateway): remap all paths in system service unit to target user's home

    merged to main

    files: hermes_cli/gateway.py, tests/hermes_cli/test_gateway_service.py

    Show 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
  • Closed#6989[Bug] System gateway service fails with CHDIR error when installed as root — path mismatch between User= and WorkingDirectory/ExecStart
  • Closed#25282hermes update resets gateway systemd unit paths to CLI user home, breaking service account setups
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.00 means 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 Lab

At 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.

For your agent

The same facts, typed.