fix(gateway): remove -k flag from kickstart in launchd_restart to prevent double-spawn - #42450
Open
liuhao1024 wants to merge 2 commits into
Open
fix(gateway): remove -k flag from kickstart in launchd_restart to prevent double-spawn#42450liuhao1024 wants to merge 2 commits into
liuhao1024 wants to merge 2 commits into
Conversation
…vent double-spawn The -k flag in launchctl kickstart re-terminates the service and then starts it. Since launchd_restart() already sends SIGTERM and waits for the gateway to exit, the -k flag races with KeepAlive=true: 1. terminate_pid() sends SIGTERM → gateway begins shutdown 2. kickstart -k detects running process → terminates again 3. KeepAlive=true detects termination → respawns 4. kickstart also starts a new instance 5. Result: two concurrent gateway processes All other kickstart calls in the codebase (launchd_start paths at L3390, L3405, L3421, and the fallback at L3560) already use plain kickstart without -k. Fixes NousResearch#42446
4 tasks
The -k flag was removed from kickstart in the production code to prevent double-spawn, but two tests still asserted on the old command with -k. Update both tests to match the new kickstart behavior.
Closed
12 tasks
19 tasks
teknium1
reviewed
Jul 14, 2026
teknium1
left a comment
Contributor
There was a problem hiding this comment.
Thanks for the focused macOS report and regression coverage.
Problems
launchd_restart()deliberately retainskickstart -kfor the failed-drain branch: current main logs a forced restart when_wait_for_gateway_exit()returns false athermes_cli/gateway.py:4347-4351, then runskickstart -kathermes_cli/gateway.py:4352. Making the command plain unconditionally removes that fallback.- The added tests do not exercise
_wait_for_gateway_exit(...)=False;test_terminate_called_before_kickstartverifies calls occurred, not their ordering or the timeout behavior.
Suggested changes
- Split the command choice by the drain result: preserve a hard restart after timeout, and cover both branches in
tests/hermes_cli/test_gateway_service.py's existing ordered-call tests.
Automated hermes-sweeper review.
| f"⚠ Gateway drain timed out after {drain_timeout:.0f}s — forcing launchd restart" | ||
| ) | ||
| subprocess.run(["launchctl", "kickstart", "-k", target], check=True, timeout=90) | ||
| # Use plain kickstart (no -k) — the SIGTERM + wait above already |
Contributor
There was a problem hiding this comment.
Please keep a forced-restart branch when _wait_for_gateway_exit() returns False. Current main explicitly logs that the drain timed out and then uses kickstart -k to force the restart; making this plain unconditionally removes that fallback.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Fixes
launchd_restart()to use plainkickstart(without-kflag), preventing a double-spawn race condition on macOS 26+ whenKeepAlive=trueis set in the LaunchAgent plist.Related Issue
Fixes #42446
Type of Change
Root Cause
In
launchd_restart()(hermes_cli/gateway.py), the code already performs graceful termination:terminate_pid()sends SIGTERM to the gateway_wait_for_gateway_exit()waits for the process to exitThen
kickstart -kis called, but-kmeans "terminate if running, then start." This races withKeepAlive=true:kickstart -kdetects running process → terminates againKeepAlive=truedetects termination → respawnskickstartalso starts a new instanceChanges Made
hermes_cli/gateway.py: Removed-kflag fromkickstartinlaunchd_restart()(line 3540), since the preceding code already handles termination. This matches the pattern used by all otherkickstartcalls in the codebase (launchd_startpaths and the fallback path in the same function).tests/cli/test_launchd_restart_kickstart.py: Added 4 regression tests verifying the kickstart command does not include-k, that termination happens before kickstart, and that self-restart (SIGUSR1) skips kickstart entirely.Testing
Checklist