Skip to content

fix(gateway): relaunch macOS launchd gateway after /restart - #37094

Closed
JiraiyaETH wants to merge 1 commit into
NousResearch:mainfrom
JiraiyaETH:fix/launchd-restart-relaunch
Closed

fix(gateway): relaunch macOS launchd gateway after /restart#37094
JiraiyaETH wants to merge 1 commit into
NousResearch:mainfrom
JiraiyaETH:fix/launchd-restart-relaunch

Conversation

@JiraiyaETH

Copy link
Copy Markdown

Problem

On macOS, sending /restart to a launchd-managed gateway can leave it dead until manual recovery.

_handle_restart_command selects its restart strategy from INVOCATION_ID (systemd) and /.dockerenv (container) only:

_under_service = bool(os.environ.get("INVOCATION_ID"))   # systemd sets this
_in_container  = os.path.exists("/.dockerenv") or os.path.exists("/run/.containerenv")
if _under_service or _in_container:
    self.request_restart(detached=False, via_service=True)   # exit 75 -> supervisor relaunches
else:
    self.request_restart(detached=True,  via_service=False)  # detached helper + clean exit 0

launchd sets neither variable, so a LaunchAgent-managed gateway takes the else branch: it spawns a detached helper and exits 0. The generated plist uses KeepAlive{SuccessfulExit: false}, which treats a clean exit as success and does not relaunch — and the detached helper does not reliably survive under launchd. Net: /restart silently kills the gateway.

This is the macOS manifestation behind #29180, #9659, and #35043.

Fix

Detect launchd via XPC_SERVICE_NAME — the launchd analog of systemd's INVOCATION_ID (set to the job label for managed jobs, "0" for shell-launched processes) — and route those restarts through the existing via_service=True path, which already exits 75 on darwin so KeepAlive relaunches:

_under_launchd = sys.platform == "darwin" and os.environ.get("XPC_SERVICE_NAME", "0") not in ("", "0")
if _under_service or _under_launchd or _in_container:
    ...

No plist change required — the macOS exit-75 path already exists; this just lets launchd-managed gateways reach it.

Preserved behavior

  • Intentional gateway stop (clean exit 0) still stays down.
  • Bare-process restarts (no XPC_SERVICE_NAME) still use the detached path.
  • systemd / container behavior unchanged.

Testing

Adds two cases to tests/e2e/test_platform_commands.py:

  • launchd env → request_restart(detached=False, via_service=True)
  • bare process (XPC_SERVICE_NAME="0") → request_restart(detached=True, via_service=False)

Refs #29180, #9659, #35043

On macOS, `/restart` selected its restart strategy from `INVOCATION_ID`
(systemd) and `/.dockerenv` (container) only. launchd sets neither, so a
launchd-managed gateway fell through to the detached self-restart path:
the process exits 0, and the generated plist's `KeepAlive{SuccessfulExit:
false}` treats a clean exit as success and never relaunches it. The
detached helper also does not reliably survive under launchd, so
`/restart` could silently leave the gateway down until manual recovery.

Detect launchd via `XPC_SERVICE_NAME` (the launchd analog of
`INVOCATION_ID`; it is "0" for shell-launched processes) and route those
restarts through the existing service path, which already exits 75 on
darwin so `KeepAlive` relaunches it. An intentional `gateway stop` (clean
exit 0) still stays down, and bare-process restarts still use the
detached path. No plist change is required.

Adds unit coverage: launchd -> via_service=True; bare process -> detached.

Refs NousResearch#29180, NousResearch#9659, NousResearch#35043

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@alt-glitch alt-glitch added type/bug Something isn't working comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists labels Jun 2, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for the precise macOS launchd investigation. This is an automated hermes-sweeper review; the requested behavior is already on current main.

  • gateway/slash_commands.py:1302-1309 detects launchd via XPC_SERVICE_NAME while excluding the interactive-shell sentinel "0", then selects request_restart(detached=False, via_service=True).
  • tests/gateway/test_restart_service_detection.py:46-64 covers both the launchd service path and the bare macOS shell fallback.
  • Commit abc3662bf6076045e4d4dc1e14a74cb35d69b86e (fix(gateway): detect launchd in /restart service-manager probe (#43475)) implemented this exact fix and is included in v2026.7.1.

The original implementation was in gateway/run.py; the handler now lives in gateway/slash_commands.py, so this PR's code and tests are superseded.

@teknium1 teknium1 closed this Jul 13, 2026
@teknium1 teknium1 added the sweeper:implemented-on-main Sweeper: behavior already present on current main label Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists sweeper:implemented-on-main Sweeper: behavior already present on current main type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants