Avoid double-starting the macOS gateway after launchd plist refresh#5110
Avoid double-starting the macOS gateway after launchd plist refresh#5110RaviTharuma wants to merge 2 commits into
Conversation
On macOS the gateway plist uses RunAtLoad. Refreshing the plist unloads and reloads the job, which already starts the gateway again. Calling launchctl start immediately after that can race a second local gateway process into existence and create token/session lock conflicts across Telegram, Discord, and WhatsApp.\n\nConstraint: launchd reload semantics differ from systemd and the Hermes plist has RunAtLoad enabled.\nRejected: Keep the extra launchctl start for symmetry with the non-refresh path | can spawn duplicate local gateway instances.\nConfidence: medium\nScope-risk: narrow\nDirective: If refresh logic changes, preserve the rule that unload+load with RunAtLoad must not be followed by an unconditional second start.\nTested: python -m py_compile hermes_cli/gateway.py; manual stop/start lifecycle on macOS\nNot-tested: Automated launchd regression test in CI
|
LGTM. Clean, correct, targeted fix for a real race. Verified the logic:
The fix correctly gates on the return value. Comment explains the reasoning. Nothing to add to the logic itself. One tiny defensive improvement to consider:
subprocess.run(["launchctl", "unload", str(plist_path)], check=False)
subprocess.run(["launchctl", "load", str(plist_path)], check=False)It returns Low probability in practice, and matches the existing behavior of the function. Worth noting for a future iteration, but not a blocker. Ship it. This is exactly the kind of race I was debugging with my own launchd services earlier today — the combination of |
refresh_launchd_plist_if_needed() now returns False when launchctl load fails, so launchd_start() falls through to the normal error-handled start path instead of falsely printing 'Service started'.
|
Good catch — addressed in 2b95c2b. |
Summary
launchctl startafter a plist refresh already performedunload+loadWhy
The Hermes launchd plist uses
RunAtLoad. Reloading the plist already starts the gateway again, so an immediate extralaunchctl startcan race a second local gateway process into existence and trigger token/session lock conflicts.Fixes #5109
Verification
python -m py_compile hermes_cli/gateway.pyNot run