2026-05-15 OpenClaw LaunchAgent ThrottleInterval, KeepAlive, and SuccessfulExit on a rented ProxyMac Mac mini: stop crash loops from masquerading as model outages
Teams running OpenClaw as a LaunchAgent on rented Mac mini M4 hosts in Hong Kong, Japan, Korea, Singapore, or the United States hit a cruel pattern: the gateway exits, launchd relaunches it in under a second, providers return 429 or empty streams, and everyone blames “the model.” This article is for operators who need (1) a crisp audience filter, (2) symptom separation between crash loops and quota throttles, (3) a four-row decision matrix for ThrottleInterval, KeepAlive, and SuccessfulExit, (4) a nine-step runbook aligned with restart recovery and dev/staging/prod isolation, (5) a scenario table for HTTP 429 bursts, plus (6) pitfalls when ThrottleInterval is left at 0. Pair with ulimits & memory, unattended jobs, and corporate HTTP proxy encoding. For SKU sizing use pricing; for incident templates use help; for one-off GUI trust prompts use VNC.
Who must tune ThrottleInterval and KeepAlive for OpenClaw
Start with fleets where the gateway process can exit for reasons unrelated to model quality—bad config JSON, MCP child segfaults, TLS MITM rotation, or token files that disappear mid-deploy. If your LaunchAgent plist omits ThrottleInterval, macOS defaults can allow tight respawn loops that hammer inference APIs faster than any human would retry in SSH. Conversely, if you set KeepAlive without pairing exit semantics, a “successful” maintenance stop may still trigger an immediate respawn you did not intend.
- Quantitative gate: if
log show --predicate 'process == "launchd"' --last 15mshows more than 12 start events for the same label, you are in crash-loop territory. - Provider gate: when 429 responses correlate with restart timestamps within ±5 s, throttle relaunches before tuning model temperature.
- Isolation gate: multi-env fleets should never share one plist label—mirror the label-per-env discipline from isolation article before editing throttle knobs.
Symptoms: crash loops vs provider-side throttles
Crash loops often show sawtooth CPU: 0% idle, 180% for ~8 seconds while OpenClaw boots, hard exit, repeat. Provider throttles instead show steady CPU with log lines referencing retry-after or exponential backoff hints. JSONL may show identical first-line banners across dozens of files when the process never reaches steady state. Before opening a ticket with your model vendor, diff restart cadence against plist timestamps using stat -f '%m' ~/Library/LaunchAgents/com.example.openclaw.plist and correlate with JSONL diagnostics patterns.
launchctl print last exit status, (2) HTTP status histogram from gateway logs, (3) MCP child exit codes—if MCP is stable while HTTP is 429-heavy, throttle relaunches first.
Decision matrix: ThrottleInterval vs KeepAlive vs SuccessfulExit
| Knob | What it controls | When to raise / enable | Primary risk if mis-set |
|---|---|---|---|
ThrottleInterval (seconds) |
Minimum spacing between automatic restarts after exit | After any 429 storm or flaky MCP binary during rollout | Too high masks real recovery during incidents |
KeepAlive true |
Respawn when job exits (success or failure per plist) | Long-lived gateways that must survive reboots | Infinite respawn without throttle burns quota |
SuccessfulExit false |
Treat exit 0 as success that should not respawn when paired with KeepAlive semantics | Maintenance windows using sentinel scripts | Wrong boolean keeps zombie restart loops |
Manual launchctl kickstart -k |
Operator-driven restart bypassing throttle cadence | Controlled deploy after plist validation | Automation calling kickstart in a tight loop replicates the storm |
Nine-step stabilization runbook
- Label audit: ensure only one plist owns prod OpenClaw per env isolation guidance.
- Capture exit storms: export last 200 lines of unified log filtered by label.
- Set baseline throttle: start with 10 seconds in dev, 30 seconds in prod after first 429 cluster.
- Define SuccessfulExit: document exit 0 for graceful shutdown scripts used during upgrades.
- Pair memory ceilings: align with ulimit article so OOMs do not masquerade as network faults.
- Webhook suppression: if using Slack webhooks, debounce alerts when throttle is actively cooling the process.
- Fault injection: temporarily move aside a non-secret config file, confirm restart spacing obeys ThrottleInterval.
- Soak: run 45 minutes of synthetic traffic at 40% peak parallel to ensure no hidden watchdog doubles launches.
- Postmortem: store plist diff and restart histogram in the same Git repo as config versioning.
ThrottleInterval to 0 on shared tenant minis attached to production API keys—your crash becomes everyone’s rate-limit story.
Scenario table: HTTP 429 bursts vs launchd behavior
| Scenario | Observable pattern | First lever |
|---|---|---|
| MCP segfault loop | Exit code 139 repeating every 2–4 s | Raise ThrottleInterval to 30; fix MCP binary path |
| Token file race during deploy | Exit 78 for 6 cycles then success | Add deploy mutex; keep ThrottleInterval ≥ 15 |
| Provider global throttle | 429 with Retry-After: 60 |
Set ThrottleInterval ≥ 60; reduce parallel agents |
Operator kickstart -k script bug |
Starts every 60 s regardless of health | Remove cron wrapper; rely on KeepAlive + health probe |
Pitfalls: ThrottleInterval at zero and duplicate watchdogs
Some copy-pasted plists inherit ThrottleInterval 0 from templates meant for interactive daemons. OpenClaw plus aggressive KeepAlive then produces sub-second restart storms that exhaust file descriptors faster than ulimits allow—surfacing as MCP 24 errors unrelated to tool logic. A second pitfall is stacking an external cron job that calls launchctl kickstart while KeepAlive is true: you double-invoke during healthy periods. Choose one supervision layer.
defaults read /path/to.plist ThrottleInterval
FAQ
Does ThrottleInterval slow down legitimate deploys? It only enforces a minimum wall-clock delay between automatic relaunches after exit. Manual launchctl kickstart from an operator bypasses the throttle for controlled changes.
Should OpenClaw use KeepAlive true during canary upgrades? Often yes for long-running gateways, but pair it with ThrottleInterval so a bad binary cannot respawn instantly; use a second plist label for canary as in dev/staging isolation guides.
What exit code should be marked SuccessfulExit for maintenance stops? Pick a sentinel your wrapper emits—commonly 0 for graceful shutdown—and document it beside the plist so on-call engineers do not confuse it with crash signatures.
Why ProxyMac Mac mini is the right place to rehearse launchd restart discipline
Rented Mac mini M4 nodes give predictable single-tenant CPU so you can distinguish thermal throttling from restart storms, native macOS syslog behavior identical to laptops your developers already trust, and region placement across HK / JP / KR / SG / US so latency experiments mirror production user bases. When throttle math is validated here, you promote the same plist snippets through pricing-transparent capacity adds rather than guessing on shared CI runners that hide launchd semantics behind containers.
Harden OpenClaw launchd before quotas bite
ThrottleInterval · KeepAlive · HK / JP / KR / SG / US