#11327: fix(launchd): reload plist from disk on restartLaunchAgent
gateway
Cluster:
Gateway Restart Improvements
## Summary
`restartLaunchAgent()` uses only `launchctl kickstart -k`, which restarts the process but does **not** reload the plist from disk. After `openclaw update` rewrites the plist with new environment variables (e.g., `OPENCLAW_SERVICE_VERSION`), launchd continues using the stale in-memory definition.
## Fix
When the plist file exists on disk, `restartLaunchAgent` now performs a full cycle:
1. `launchctl bootout` — unload the old definition
2. `launchctl enable` — clear any persisted disabled state
3. `launchctl bootstrap` — load the new plist from disk
4. `launchctl kickstart -k` — start the process
This matches the pattern already used by `installLaunchAgent()`. Falls back to kickstart-only when no plist is found on disk.
## Testing
- Verified the source matches the minified `dist/service-BZesBIaG.js` (lines 292-302)
- The fix follows the same bootout → enable → bootstrap → kickstart pattern used in `installLaunchAgent()`
- Fallback to kickstart-only preserves behavior when plist is absent
Fixes #11324
## Related
- #8790 — Version mismatch between `gateway run` and launchctl service
- #3780 — `gateway stop` uses `bootout`, breaking subsequent `gateway start`
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
This change updates `restartLaunchAgent()` to reload a LaunchAgent plist from disk when it exists, by running a `bootout → enable → bootstrap → kickstart -k` cycle. If the plist isn’t present, it preserves the prior behavior and only runs `kickstart -k`.
The intent matches the pattern already used in `installLaunchAgent()` to ensure updated plist contents (notably environment variables) take effect after `openclaw update`.
<h3>Confidence Score: 4/5</h3>
- Mostly safe to merge, but error handling in the new restart path can misreport success and mask launchctl failures.
- The change is localized and follows an existing install pattern, but it introduces a concrete behavioral bug: in the plist-present branch, several launchctl commands’ non-zero exit codes are ignored, so the CLI can claim a restart succeeded when it didn’t.
- src/daemon/launchd.ts
<!-- greptile_other_comments_section -->
<sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#16845: fix(daemon): gateway auto-restart on SIGTERM + agent restart guidel...
by kiminbean · 2026-02-15
83.6%
#19857: fix(launchd): self-heal restart when service is unloaded
by vibecodooor · 2026-02-18
82.7%
#15619: fix: clean up orphan LaunchAgent plist on bootstrap failure
by superlowburn · 2026-02-13
82.6%
#7155: fix(gateway): use kill SIGTERM instead of bootout for stop
by rafaelreis-r · 2026-02-02
82.4%
#21591: fix(update): prevent double restart when refreshing service env
by irchelper · 2026-02-20
81.2%
#22224: fix(launchd/macos): prevent restart loop by using KeepAlive.Success...
by ashiabbott · 2026-02-20
79.0%
#22304: Gateway: fix launchd start after stop
by apethree · 2026-02-21
78.8%
#20272: fix: LaunchAgent KeepAlive causes restart loop (fixes #20257)
by MisterGuy420 · 2026-02-18
78.3%
#18236: macOS daemon: bootstrap LaunchAgent on gateway start after stop
by agisilaos · 2026-02-16
77.6%
#8260: fix(macOS): gateway readiness detection + reversible Configure later
by xksteven · 2026-02-03
74.5%