#18236: macOS daemon: bootstrap LaunchAgent on gateway start after stop
gateway
cli
stale
size: M
Cluster:
Gateway and macOS Improvements
## Summary
Fixes a macOS daemon lifecycle bug where `openclaw gateway start` incorrectly reported the service as not installed after `openclaw gateway stop`.
Closes #17763.
## Root cause
On macOS, `gateway stop` runs `launchctl bootout`, which unloads the LaunchAgent but does not remove its plist.
The start flow only handled the `loaded` case (restart), and when unloaded it returned early with install hints instead of attempting bootstrap.
## Changes
- Added explicit `start` operation to the `GatewayService` interface.
- Implemented macOS `start` behavior via `startLaunchAgent`:
- if loaded: restart with `kickstart -k`
- if unloaded and plist exists: `bootstrap` + `kickstart -k`
- if plist missing: throw clear error
- Updated CLI lifecycle start path to call `service.start(...)` when service is unloaded before falling back to hints.
- Added regression test coverage for unloaded-but-installed LaunchAgent start behavior.
## Verification
### Automated
- `pnpm test src/daemon/launchd.test.ts`
### Manual repro (macOS)
```bash
export OPENCLAW_PROFILE=bug17763
export OPENCLAW_STATE_DIR="$HOME/.openclaw-bug17763"
pnpm openclaw gateway install --force
pnpm openclaw gateway stop
pnpm openclaw gateway start
pnpm openclaw gateway status --no-probe
```
Observed after fix:
- `gateway start` reports `Started LaunchAgent: gui/<uid>/ai.openclaw.bug17763`
- `gateway status --no-probe` reports `Service: LaunchAgent (loaded)`
- no reinstall required between stop and start
## Commits
- `Daemon: start launch agent when unloaded`
- `Tests: cover launchd start bootstrap path`
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Fixes macOS daemon lifecycle bug where `gateway start` failed after `gateway stop` by adding explicit `start` operation that bootstraps unloaded LaunchAgents. Also fixes IPv6 websocket URL handling in pairing setup codes and Swift deep links.
**Main Changes:**
- Added `start()` method to `GatewayService` interface across all platforms (macOS/Linux/Windows)
- macOS `startLaunchAgent`: bootstraps + kickstarts when plist exists but service is unloaded
- CLI lifecycle: calls `service.start()` when unloaded, only shows install hints if that fails with expected errors
- Swift `GatewayConnectDeepLink`: normalizes IPv6 hosts (strips/adds brackets as needed) for proper websocket URL construction
- TypeScript pairing: changed from `hostname` to `host` to preserve port in IPv6 URLs
**Test Coverage:**
- New test for unloaded LaunchAgent bootstrap path
- Swift IPv6 deep link tests for setup code parsing and bracketed host input
- TypeScript pairing test for IPv6 URL preservation
<h3>Confidence Score: 4/5</h3>
- This PR is safe to merge with minimal risk
- The PR addresses a clear bug with well-scoped changes. The daemon lifecycle fix follows the existing pattern (restart/stop) and adds proper error handling. IPv6 fixes are straightforward string manipulation. Test coverage is good for both changes. The only minor concern is that the CLI error handling catches errors broadly, but this is mitigated by the `isExpectedNotLoadedStartError` check.
- No files require special attention
<sub>Last reviewed commit: 8d59787</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#22304: Gateway: fix launchd start after stop
by apethree · 2026-02-21
88.1%
#8260: fix(macOS): gateway readiness detection + reversible Configure later
by xksteven · 2026-02-03
82.0%
#16845: fix(daemon): gateway auto-restart on SIGTERM + agent restart guidel...
by kiminbean · 2026-02-15
81.0%
#7155: fix(gateway): use kill SIGTERM instead of bootout for stop
by rafaelreis-r · 2026-02-02
80.6%
#11147: fix(daemon): stop gateway by port when no daemon service is active
by jasonthewhale · 2026-02-07
79.1%
#13084: fix(daemon): multi-layer defense against zombie gateway processes
by openperf · 2026-02-10
78.2%
#15619: fix: clean up orphan LaunchAgent plist on bootstrap failure
by superlowburn · 2026-02-13
77.9%
#11327: fix(launchd): reload plist from disk on restartLaunchAgent
by caiop91 · 2026-02-07
77.6%
#22313: fix: check plist existence before marking LaunchAgent missing
by MisterGuy420 · 2026-02-21
77.5%
#23584: fix(daemon): improve gateway service detection to avoid false posit...
by mohandshamada · 2026-02-22
77.3%