{"repo":"NousResearch/hermes-agent","public_id":"14051","display_label":"Gateway service restart exit code and systemd retry fixes","summary":"These issues and PRs address how the gateway service signals restarts to systemd, including using a dedicated exit code to avoid exhausting StartLimitBurst and ensuring the systemd unit retries with appropriate backoff. Related changes also cover zombie PID detection during replacement and consistent use of the managed restart exit code on startup failure.","subsystem":"gateway","counts":{"members":5,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":3},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":18639},"resolution":{"state":"unresolved","by":null},"freshness":{"state":"verified","last_verified_at":"2026-05-02T03:21:30+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.4},"fix_edges":[{"issue_number":14051,"pr_number":18639,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false}],"members":[{"kind":"issue","number":14051,"title":"Gateway service: exit code mismatch causes StartLimitBurst=5 exhaustion on rapid restart","url":null,"state":"closed"},{"kind":"pr","number":14069,"title":"fix(gateway): exit with RestartForceExitStatus code and detect zombie PIDs in --replace","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#14051","number":14051},{"kind":"pr","value":"#13655","number":13655},{"kind":"pr","value":"#13655","number":13655}],"category":"uncategorized","implication":"needs_llm"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":14080,"title":"fix(gateway): use managed restart exit code on startup failure","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#14051","number":14051},{"kind":"pr","value":"#18639","number":18639}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":14260,"title":"fix(gateway): use GATEWAY_SERVICE_RESTART_EXIT_CODE instead of hardcoded 1","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#14051","number":14051},{"kind":"pr","value":"#18639","number":18639}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":18639,"title":"fix: gateway systemd unit now retries indefinitely with backoff","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["hermes_cli/gateway.py"],"fix_hunks":[{"file":"hermes_cli/gateway.py","hunks":["@@ -188,7 +188,7 @@ def _graceful_restart_via_sigusr1(pid: int, drain_timeout: float) -> bool:\n \n     SIGUSR1 is wired in gateway/run.py to ``request_restart(via_service=True)``\n     which drains in-flight agent runs (up to ``agent.restart_drain_timeout``\n-    seconds), then exits with code 75.  Both systemd (``Restart=on-failure``\n+    seconds), then exits with code 75.  Both systemd (``Restart=always``\n     + ``RestartForceExitStatus=75``) and launchd (``KeepAlive.SuccessfulExit\n     = false``) relaunch the process after the graceful exit.\n ","@@ -1655,8 +1655,7 @@ def generate_systemd_unit(system: bool = False, run_as_user: str | None = None)\n Description={SERVICE_DESCRIPTION}\n After=network-online.target\n Wants=network-online.target\n-StartLimitIntervalSec=600\n-StartLimitBurst=5\n+StartLimitIntervalSec=0\n \n [Service]\n Type=simple","@@ -1670,8 +1669,10 @@ def generate_systemd_unit(system: bool = False, run_as_user: str | None = None)\n Environment=\"PATH={sane_path}\"\n Environment=\"VIRTUAL_ENV={venv_dir}\"\n Environment=\"HERMES_HOME={hermes_home}\"\n-Restart=on-failure\n-RestartSec=30\n+Restart=always\n+RestartSec=60\n+RestartMaxDelaySec=300\n+RestartSteps=5\n RestartForceExitStatus={GATEWAY_SERVICE_RESTART_EXIT_CODE}\n KillMode=mixed\n KillSignal=SIGTERM","@@ -1691,9 +1692,9 @@ def generate_systemd_unit(system: bool = False, run_as_user: str | None = None)\n     sane_path = \":\".join(path_entries)\n     return f\"\"\"[Unit]\n Description={SERVICE_DESCRIPTION}\n-After=network.target\n-StartLimitIntervalSec=600\n-StartLimitBurst=5\n+After=network-online.target\n+Wants=network-online.target\n+StartLimitIntervalSec=0\n \n [Service]\n Type=simple","@@ -1702,8 +1703,10 @@ def generate_systemd_unit(system: bool = False, run_as_user: str | None = None)\n Environment=\"PATH={sane_path}\"\n Environment=\"VIRTUAL_ENV={venv_dir}\"\n Environment=\"HERMES_HOME={hermes_home}\"\n-Restart=on-failure\n-RestartSec=30\n+Restart=always\n+RestartSec=60\n+RestartMaxDelaySec=300\n+RestartSteps=5\n RestartForceExitStatus={GATEWAY_SERVICE_RESTART_EXIT_CODE}\n KillMode=mixed\n KillSignal=SIGTERM","@@ -2451,7 +2454,7 @@ def run_gateway(verbose: int = 0, quiet: bool = False, replace: bool = False):\n     print()\n     \n     # Exit with code 1 if gateway fails to connect any platform,\n-    # so systemd Restart=on-failure will retry on transient errors\n+    # so systemd Restart=always will retry on transient errors\n     verbosity = None if quiet else verbose\n     try:\n         success = asyncio.run(start_gateway(replace=replace, verbosity=verbosity))\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}