AI / Automation May 21, 2026

OpenClaw Gateway launchd Startup on Mac mini: Absolute node Path, ProcessType & Exit 78 Fix (2026-05-21)

ProxyMac Engineering Team May 21, 2026 ~18 min read

After reboot, your OpenClaw gateway LaunchAgent on a rented Mac mini M4 in Hong Kong, Japan, Korea, Singapore, or the United States may never bind its admin port, exit with code 78 in launchctl list, or take roughly three minutes before WebSocket clients stop seeing 1006 abnormal closures. This May 21, 2026 runbook targets launchd plist mistakes—a bare node in ProgramArguments and a missing ProcessType of Interactive—not MCP orphan trees. It pairs with gateway restart recovery, headless SSH first boot, and Node runtime / nvm alignment.

Exit 78, slow listen, and WebSocket 1006 on cold boot

Community reports on Apple Silicon hosts describe two distinct launchd failure shapes. Immediate exit 78 means launchd never successfully exec’d your gateway binary—often because ProgramArguments starts with the string node while launchd’s environment has an empty or minimal PATH. Delayed readiness shows the job “running” in launchctl list but lsof finds no listener for minutes; dashboards then log WebSocket close code 1006 until the process finally schedules. Both differ from ThrottleInterval crash loops, which hammer CPU with rapid respawns.

  • Exit status 78 right after launchctl bootstrap or login—check ~/Library/LaunchAgents/*.plist for bare node.
  • 180+ seconds from boot to first successful health probe on an otherwise idle mini.
  • 1006 on the control WebSocket while SSH works and disk is healthy—listener not up yet, not TLS misconfig.
  • Manual node gateway.js in SSH works but the LaunchAgent path fails—classic PATH vs absolute binary split.
Do not conflate with Node semver drift: If node -v differs between login shell and plist, read runtime mismatch first. Exit 78 is “binary not found”; mismatch is “found but wrong ABI.”

Why launchd ignores your shell PATH and deprioritizes background agents

LaunchAgents inherit a stripped environment compared with interactive Terminal.app sessions. Documentation and field threads emphasize that EnvironmentVariables in the plist do not help resolve the interpreter name inside ProgramArguments—launchd resolves the executable before applying those keys. That is why copying a laptop plist with node as argv[0] fails on headless ProxyMac minis even when you export PATH in the same XML.

Separately, when ProcessType is omitted, macOS may treat the gateway as a background workload subject to power and scheduling heuristics after reboot. Operators report shrinking cold-boot listen time from on the order of three minutes to a few seconds after adding <key>ProcessType</key><string>Interactive</string> beside Label. Treat that as scheduling hygiene, not a license to run GUI sessions unattended—pair one-time Keychain work with VNC per first-boot checklist, then stay on SSH.

Operator matrix (signal → first fix)

Primary signalFirst response (order matters)Evidence to captureRollback if wrongOwner
Last exit code 78 on OpenClaw labelReplace argv[0] with $(command -v node) absolute path; bootout → bootstraplaunchctl print gui/$UID/<label> + plist XMLRestore prior plist from gitPlatform SRE
Job running, no listener >60 s after rebootAdd ProcessType Interactive; confirm single plist labelTimestamped lsof -nP -iTCP:<port> -sTCP:LISTENRemove ProcessType if desktop session policy forbidsAutomation lead
Duplicate listeners on admin portFollow single-listener recoveryTwo PIDs in lsof outputbootout duplicate labelOn-call
Crash loop <30 s cadence, high CPUTune ThrottleInterval / KeepAlive—not this articlelog show --predicate 'process == "launchd"' --last 5mRevert throttle keysSRE

Nine-step plist fix (SSH on ProxyMac mini)

  1. Identify the label: launchctl list | grep -i openclaw and note the full reverse-DNS name.
  2. Print live state: launchctl print gui/$(id -u)/<label> and screenshot last exit code.
  3. Resolve Node: in the same user context, run command -v node (or which node) and record the absolute path—typically under /opt/homebrew or ~/.nvm.
  4. Edit plist: set ProgramArguments argv[0] to that path; keep gateway script path absolute too.
  5. Add ProcessType: insert Interactive under the root dict if cold-boot delay matches field reports.
  6. Validate XML: plutil -lint ~/Library/LaunchAgents/<file>.plist before reload.
  7. Recycle: launchctl bootout gui/$(id -u) <label> then bootstrap the same path (or vendor kickstart).
  8. Time-to-listen: loop lsof every 5 seconds for 120 seconds; target <15 s on M4.
  9. Document: commit plist to your infra repo; link this article in the runbook for the next hire.
Example ProgramArguments shape: /opt/homebrew/bin/node + absolute path to openclaw-gateway entry script + --config + absolute config JSON—never rely on cd in a wrapper unless WorkingDirectory is set.

Verify listener, health endpoint, and WebSocket stability

After bootstrap, confirm exactly one PID listens on your configured admin port (commonly cited around 18999 in operator docs—match your config.json). Curl the HTTP health route if enabled; then attach your desktop client and ensure no 1006 within the first 30 seconds post-reboot. If health passes but MCP tools fail, pivot to MCP orphan hygiene rather than re-editing the gateway plist.

Reboot test once per quarter on automation hosts: launchd regressions often appear only after macOS security updates, not during same-day SSH edits. Log uname -r beside time-to-listen in your ticket system.

Prevention: infra-as-code plists and staging labels

  • Store plists in git with the absolute Node path templated from your image build (Homebrew prefix or nvm default).
  • Separate LaunchAgent labels for dev/staging/prod on the same mini—see restart recovery guide for port collisions.
  • CI smoke: after deploy, assert listener <20 s via SSH script before marking the host healthy.
  • Disposable lab mini in HK/JP/KR/SG/US for plist experiments—cheaper than debugging on the production orchestrator.

FAQ

Why does my OpenClaw LaunchAgent exit immediately with code 78? launchd resolves ProgramArguments before applying EnvironmentVariables. A bare node string fails when PATH is empty under launchd. Replace it with the absolute path from command -v node, then bootout and bootstrap the plist again.

Why does the gateway take minutes to listen after reboot? Default LaunchAgent plists may omit ProcessType Interactive, letting macOS deprioritize background startup for minutes. Adding ProcessType Interactive under the main dict often drops time-to-listen from roughly three minutes to a few seconds on Apple Silicon minis.

How is this different from ThrottleInterval crash loops? ThrottleInterval issues show rapid respawn storms with high CPU. Exit 78 is a one-shot config failure before the gateway runs. Slow listen without crash loops points to ProcessType or resource scheduling—not KeepAlive fighting a bad binary.

Why rented Mac mini is the right place to harden OpenClaw launchd

Gateway plists are infrastructure: they must survive reboot, OS updates, and engineers who only know laptop Homebrew paths. Apple Silicon M4 minis give predictable cold-boot timing, macOS launchd matches OpenClaw’s documented LaunchAgent flow, and HK / JP / KR / SG / US placement keeps control-plane latency near the APIs you automate. ProxyMac lets you clone a known-good plist on a staging mini, prove sub-minute listen under SSH, then promote the same XML to production—see pricing for region choice and help center for access patterns.

Prove launchd plists on staging metal

Rent HK / JP / KR / SG / US Mac mini for OpenClaw gateway hardening