{"repo":"NousResearch/hermes-agent","public_id":"22035","display_label":"Gateway --system CLI reads wrong HERMES_HOME","summary":"A bug where gateway --system operations (e.g. restart) incorrectly read status from root's HERMES_HOME instead of the unit's, causing false failure reports. The members include the bug report and related fix attempts.","subsystem":null,"counts":{"members":3,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":1},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":22803,"merged_at":"2026-05-09T20:38:39+00:00"},"resolution":{"state":"resolved","by":"pr-22803"},"freshness":{"state":"verified","last_verified_at":"2026-05-09T20:38:39+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.6666666666666666},"fix_edges":[{"issue_number":22035,"pr_number":22803,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-05-09T20:38:39+00:00"}],"members":[{"kind":"issue","number":22035,"title":"[Bug]: gateway restart --system always reports failure (60s timeout × 2) — wrapper reads runtime status from root's HERMES_HOME","url":null,"state":"closed"},{"kind":"pr","number":22533,"title":"fix(gateway): adopt unit's HERMES_HOME for --system CLI ops","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#22803","number":22803}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":22803,"title":"fix(gateway): adopt unit's HERMES_HOME for --system CLI ops (salvage #22533)","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["hermes_cli/gateway.py","scripts/release.py"],"fix_hunks":[{"file":"hermes_cli/gateway.py","hunks":["@@ -635,6 +635,66 @@ def _probe_systemd_service_running(system: bool = False) -> tuple[bool, bool]:\n     return selected_system, result.stdout.strip() == \"active\"\n \n \n+def _read_systemd_unit_environment(system: bool = False) -> dict[str, str]:\n+    \"\"\"Parse the gateway unit's ``Environment=`` directives.\n+\n+    ``systemctl show -p Environment`` returns a single line of\n+    space-separated ``KEY=VALUE`` pairs; values are not quoted in the output\n+    even when the unit file quoted them. We split on whitespace and ``=``.\n+    \"\"\"\n+    selected_system = _select_systemd_scope(system)\n+    try:\n+        result = _run_systemctl(\n+            [\n+                \"show\",\n+                get_service_name(),\n+                \"--no-pager\",\n+                \"--property\",\n+                \"Environment\",\n+            ],\n+            system=selected_system,\n+            capture_output=True,\n+            text=True,\n+            timeout=10,\n+        )\n+    except (RuntimeError, subprocess.TimeoutExpired, OSError):\n+        return {}\n+    if result.returncode != 0:\n+        return {}\n+    parsed: dict[str, str] = {}\n+    for line in result.stdout.splitlines():\n+        if not line.startswith(\"Environment=\"):\n+            continue\n+        body = line[len(\"Environment=\"):].strip()\n+        for token in body.split():\n+            if \"=\" not in token:\n+                continue\n+            key, value = token.split(\"=\", 1)\n+            parsed[key] = value\n+    return parsed\n+\n+\n+def _sync_hermes_home_from_systemd_unit(system: bool) -> None:\n+    \"\"\"When acting on a system-scope unit, adopt its ``HERMES_HOME``.\n+\n+    Under ``sudo``, ``HERMES_HOME`` is stripped and ``HOME=/root``, so\n+    :func:`get_hermes_home` falls back to ``/root/.hermes`` — the wrong\n+    profile. The unit file pins ``HERMES_HOME`` for the actual gateway\n+    process, so we mirror that into our own environment to make\n+    ``read_runtime_status`` / ``get_running_pid`` read the correct files.\n+    \"\"\"\n+    if not system:\n+        return\n+    env = _read_systemd_unit_environment(system=True)\n+    unit_home = env.get(\"HERMES_HOME\", \"\").strip()\n+    if not unit_home:\n+        return\n+    current = os.environ.get(\"HERMES_HOME\", \"\").strip()\n+    if current == unit_home:\n+        return\n+    os.environ[\"HERMES_HOME\"] = unit_home\n+\n+\n def _read_systemd_unit_properties(\n     system: bool = False,\n     properties: tuple[str, ...] = (","@@ -2380,6 +2440,7 @@ def systemd_stop(system: bool = False):\n     if system:\n         _require_root_for_system_service(\"stop\")\n     _require_service_installed(\"stop\", system=system)\n+    _sync_hermes_home_from_systemd_unit(system=system)\n     try:\n         from gateway.status import get_running_pid, write_planned_stop_marker\n         pid = get_running_pid(cleanup_stale=False)","@@ -2408,6 +2469,7 @@ def systemd_restart(system: bool = False):\n         _preflight_user_systemd()\n     _require_service_installed(\"restart\", system=system)\n     refresh_systemd_unit_if_needed(system=system)\n+    _sync_hermes_home_from_systemd_unit(system=system)\n     from gateway.status import get_running_pid\n \n     pid = get_running_pid() or _systemd_main_pid(system=system)","@@ -2503,6 +2565,8 @@ def systemd_status(deep: bool = False, system: bool = False, full: bool = False)\n         print(f\"  Run: {'sudo ' if system else ''}hermes gateway install{scope_flag}\")\n         return\n \n+    _sync_hermes_home_from_systemd_unit(system=system)\n+\n     if has_conflicting_systemd_units():\n         print_systemd_scope_conflict_warning()\n         print()"]},{"file":"scripts/release.py","hunks":["@@ -66,6 +66,7 @@\n     \"[redacted:email]\": \"qWaitCrypto\",\n     \"[redacted:email]\": \"Qwinty\",\n     \"[redacted:email]\": \"CalmProton\",\n+    \"308068+mbac@users.noreply.github.com\": \"mbac\",\n     \"[redacted:email]\": \"wesleysimplicio\",\n     \"[redacted:email]\": \"SiliconID\",\n     \"[redacted:email]\": \"xieNniu\",\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}