The cruelest OpenClaw failures happen after the tutorial works in your own Terminal: the LaunchAgent wakes up at boot, inherits a skeletal PATH, cannot find node, and your gateway silently respawns until API consumers see flaky 502 chains. In 2026, treat macOS launchd as a different operating system from zsh—it ignores your pretty .zprofile exports, knows nothing about nvm shims unless you wire them explicitly, and happily runs binaries with the wrong dynamic linker if you symlink carelessly. This playbook explains how to pin a toolchain, bake environment variables into the plist, and rehearse smoke tests on a rented Mac mini before you point production agents at the host.
Cross-link with openclaw doctor gateway diagnostics for deep triage, JSON env profiles for secret layering, and shared Mac mini isolation when multiple engineers share one gateway seat.
Why launchd breaks interactive assumptions
Interactive shells load rc files, direnv hooks, and editor integrations. launchd jobs inherit a curated environment documented in Apple’s plist schema—anything not listed there is undefined. That is why which node inside tmux shows /opt/homebrew/bin/node while the same user’s LaunchAgent logs complain node: command not found. The mismatch is not randomness; it is two different startup graphs.
Another footgun is HOME: automation accounts sometimes run with a different home if you cloned a provider image. OpenClaw stores state under ~/.openclaw or $OPENCLAW_HOME; if HOME drifts, the gateway writes configs nobody can find during incidents. Bake HOME explicitly when you support cloned images.
Homebrew Node versus nvm versus pinned tarballs
Homebrew installs stable paths under /opt/homebrew on Apple Silicon and keeps symlinks updated—ideal for LaunchAgents because ProgramArguments can reference /opt/homebrew/bin/node directly. nvm is ergonomic for humans but fragile for daemons: version switches depend on shell functions, not files on disk the way launchd expects. If you insist on nvm, materialize a wrapper script that exports a fixed version path and call that script from the plist—never inline source /.nvm/nvm.sh && nvm use inside ProgramArguments without a login shell.
Pinned tarballs from Node’s distribution site are popular in regulated shops: checksum the archive, unpack under /usr/local/node-20.11.1, and point the plist at that binary. Upgrades become deliberate file swaps instead of brew’s automatic relocations. Budget thirty minutes per quarter to re-pin and rerun smoke tests; skipping that invites silent drift when security patches land.
LaunchEnvironmentVariables and WorkingDirectory
Use WorkingDirectory so relative paths in OpenClaw configs resolve predictably—point it at the directory that contains openclaw.json or your approved config root. Pair it with LaunchEnvironmentVariables dict entries for PATH, NODE_OPTIONS (if allowed), and any provider SDK locations. Keep the variable list under twelve keys to reduce audit fatigue; long environment dumps usually hide typos.
StandardErrorPath and StandardOutPath should land in per-service log files with rotation; Console.app is friendly but hard to grep in CI. Log rotation at 50 MB per file with five generations is a sane default before you ship centralized logging.
Decision matrix for toolchain choices
| Approach | Daemon friendliness | Upgrade friction | Best when |
|---|---|---|---|
| Homebrew Node | High | Medium—path churn | Small teams, single gateway per host |
| nvm default alias | Low unless wrapped | Low for humans | Laptops only, not LaunchAgents |
| Pinned tarball | Very high | High—manual unpack | Compliance-heavy environments |
Five-minute smoke protocol
- Run
launchctl print gui/$UID/com.yourorg.openclaw.gatewayand confirm the last exit reason is zero. - Hit the health endpoint with
curl -fsStwice, separated by a 10 second sleep, to catch lazy TLS reloads. - Invoke a single harmless tool call that shells out to
/usr/bin/trueto verify PATH for subprocesses. - Rotate logs once to ensure permissions allow append after truncation.
- Stop the job via
launchctl bootout, start again, and ensure the gateway binds the same port within 30 seconds.
Automate the sequence with a shell script checked into your infra repository; humans forget step four until disk fills the night before a demo.
Doctor checks that catch PATH drift
The openclaw doctor command surfaces version skew, missing binaries, and suspicious environment blanks. Run it from the same account that owns the LaunchAgent, both interactively and via sudo -u if you split admin roles. Compare outputs: if interactive doctor shows Node 20.11 but the plist-launched process logs 18.19, you still have two toolchains fighting.
When doctor flags TLS store issues, fix Keychain trust before chasing OpenClaw itself—macOS updates occasionally shuffle intermediate certificates, and gateways fail with misleading “upstream 403” errors.
File permissions and umask surprises
LaunchAgents run with the user’s default umask, which often differs from the 0022 you assumed when you chmod’ed files from an interactive shell. If OpenClaw writes Unix domain sockets or pidfiles into a shared directory, verify group readability explicitly instead of relying on “it worked yesterday.” When multiple operators share a rented mini, align everyone on a single POSIX group, apply ACLs with care, and document which directories may be world-readable—security reviewers will ask, and vague answers slow shipping.
Also watch for Gatekeeper quarantine attributes on downloaded gateway binaries: com.apple.quarantine can block execution until the binary is approved, which looks like a PATH error in thin log files. Clear quarantine intentionally after checksum verification rather than blindly xattr -d’ing everything.
Upgrade windows without orphan plists
Coordinate Node upgrades with OpenClaw releases: bump the runtime first in staging, rerun smoke tests, then promote production during a maintenance window with a written rollback that includes the previous plist ProgramArguments stanza. Keep at least two generations of plist files in Git with dates so on-call engineers can diff quickly.
If you rely on global npm installs, remember npm -g paths differ between Intel and Apple Silicon homes. Never copy plist text from an Intel Mac onto Apple Silicon without revisiting every absolute path.
FAQ
Why does OpenClaw work in Terminal but fail under LaunchAgent?
Because Terminal runs your shell startup files; launchd does not. PATH and toolchain discovery must be explicit in the plist or a wrapper script.
Should I call nvm use inside a plist?
Avoid it. Use absolute binaries or a wrapper that sets a fixed NODE_HOME without interactive sourcing.
How long should first-run smoke tests run?
At least five minutes of steady traffic plus one restart cycle to catch lazy imports and permission prompts.
Where do TCC prompts still appear?
Features touching camera, microphone, contacts, or AppleScript may require GUI approval once per user—rehearse on a machine with VNC access.
First-run stability is a hardware-shaped problem: you need the same Apple Silicon behavior, the same Keychain, and the same silent fan curve your production gateway will feel. Renting a Mac mini from MacHTML for roughly $16.9 per day gives you that reference machine without procurement delays. You can SSH in for plist edits, VNC in when TCC demands a click, and tear the host down after the smoke window—elastic capacity beats leaving a misconfigured LaunchAgent on a developer laptop that sleeps at night and drops websocket sessions.
Apple Silicon efficiency also matters when you leave doctor scripts and synthetic traffic running for hours: the machine stays cool, power draw stays predictable, and you are not simulating macOS from a Linux container that lies about file locking semantics.
Rehearse OpenClaw LaunchAgents on real macOS
Rent a cloud Mac mini to validate PATH, plist bootstraps, and doctor-driven smoke tests on Apple Silicon before production cutover.