Skip to content

fix(gateway): recover unloaded launchd job during update - #43181

Closed
edufalcao wants to merge 1 commit into
NousResearch:mainfrom
edufalcao:fix/gateway-update-launchd-restart
Closed

fix(gateway): recover unloaded launchd job during update#43181
edufalcao wants to merge 1 commit into
NousResearch:mainfrom
edufalcao:fix/gateway-update-launchd-restart

Conversation

@edufalcao

@edufalcao edufalcao commented Jun 10, 2026

Copy link
Copy Markdown

Summary

  • treat planned gateway --replace takeovers as restart stops so restart lifecycle markers/notifications are preserved
  • recover hermes update on macOS when the LaunchAgent plist exists but the launchd job is currently unloaded
  • add regression coverage for the installed-but-unloaded launchd update path

Why

A macOS gateway update can leave the LaunchAgent plist installed while launchctl list <label> reports the job as unloaded. Before this change, hermes update skipped the gateway restart path in that state, leaving Telegram/cron/gateway delivery offline until the user manually ran hermes gateway start.

Planned --replace gateway takeovers are operational restarts from the user's perspective, so they should use the restart stop path to preserve the expected lifecycle marker and notification semantics.

Fixes #42006.
Related to #37388 and #42524.

Test plan

source venv/bin/activate && python -m pytest \
  tests/hermes_cli/test_cmd_update.py::TestCmdUpdateBranchFallback::test_gateway_update_starts_installed_but_unloaded_launchd_job \
  tests/gateway/test_clean_shutdown_marker.py::TestCleanShutdownMarker::test_marker_written_on_restart_stop \
  tests/gateway/test_restart_resume_pending.py::test_drain_timeout_uses_restart_reason_when_restarting \
  -q -o 'addopts='

Result: 3 passed in 9.13s

@liuhao1024

Copy link
Copy Markdown
Contributor

Verification comment from code review — this PR is clean.

The two-part fix is well-scoped:

  1. gateway/run.py: passing restart=planned_takeover to runner.stop() preserves the "restarting" / "back online" lifecycle semantics during --replace takeover.
  2. hermes_cli/main.py: when launchctl list returns non-zero (job installed but unloaded), calling launchd_start() instead of launchd_restart() recovers the edge case where a previous stop/update left the LaunchAgent dormant.

The test test_gateway_update_starts_installed_but_unloaded_launchd_job directly exercises the new code path with a returncode=3 mock (matching macOS's "not loaded" exit code). Clean, focused, no behavioral side effects on the happy path.

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/gateway Gateway runner, session dispatch, delivery area/config Config system, migrations, profiles labels Jun 10, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for the focused fix. Current main still has both target gaps: gateway/run.py:20688 calls runner.stop() without restart state after consuming a planned takeover marker, while hermes_cli/main.py:10858-10873 only restarts launchd jobs whose launchctl list probe succeeds. The proposed launchd_start() route matches the existing unloaded-job recovery in hermes_cli/gateway.py:4206-4233, and GatewayRunner.stop(restart=True) activates the restart-marker path at gateway/run.py:8398-8410.

The surrounding code has moved since the PR was opened, but the targeted expressions remain materially unchanged, so salvage should be mechanical with line-offset/context resolution.

Automated hermes-sweeper review.

@teknium1 teknium1 added sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform area/install-update Installer, updater, packaging, wheels, doctor labels Jul 14, 2026
@edufalcao edufalcao closed this by deleting the head repository Jul 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles area/install-update Installer, updater, packaging, wheels, doctor comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

macOS: launchd_restart missing bootout before bootstrap, gateway falls back to detached after update

5 participants