Summary
When the gateway is started by macOS launchd (via LaunchAgent), the Node.js process loads but hangs forever during module evaluation — never binding a port, producing any output, or responding. The exact same command, environment, and arguments work correctly when run manually (including via nohup, env -i, detached processes, or any other non-launchd method).
Environment
- macOS: 15.3.1 (Sequoia)
- Node.js: 25.5.0 (Homebrew)
- OpenClaw: 2026.3.2
- Architecture: ARM64 (Apple Silicon)
Steps to Reproduce
- Install gateway as LaunchAgent (
openclaw gateway install)
- Bootout existing stuck service:
launchctl bootout gui/$(id -u)/ai.openclaw.gateway
- Bootstrap fresh:
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/ai.openclaw.gateway.plist
- Wait 30+ seconds
- Check:
curl http://127.0.0.1:18789/ → no response
- Verify process is alive:
ps -p $(launchctl print gui/$(id -u)/ai.openclaw.gateway 2>&1 | grep pid | awk '{print $3}') -o pid,stat,etime → process running for minutes
- Check launchd state:
launchctl print gui/$(id -u)/ai.openclaw.gateway → runs = 1, pid = XXXXX, last exit code = (never exited)
Expected Behavior
Gateway starts and listens on port 18789 within ~5 seconds (as it does when run manually).
Actual Behavior
The Node.js process starts (PPID=1, managed by launchd) but:
- Never binds any network port
- Never writes to stdout, stderr, or its own log file (
/tmp/openclaw/openclaw-*.log)
- Never reads any OpenClaw config files
- Stays in
S (sleeping) state indefinitely
- Process sampling (
/usr/bin/sample) shows it is stuck in node::loader::ModuleWrap::Evaluate — V8 is trying to evaluate a JavaScript module and never completes
Detailed Investigation
We tested every variable systematically to isolate the cause:
| Test |
Result |
| Run manually with TTY |
✅ Works — starts in <2s |
Run with nohup, redirected stdin/stdout |
✅ Works |
Run with env -i (bare environment matching plist) |
✅ Works |
Run from cwd=/ (matching launchd) |
✅ Works |
Run with XPC_SERVICE_NAME=ai.openclaw.gateway set |
✅ Works |
Run with OPENCLAW_LAUNCHD_LABEL=ai.openclaw.gateway set |
✅ Works |
| Run with all of the above combined |
✅ Works |
| Minimal Node.js HTTP server via launchd |
✅ Works |
| OpenClaw gateway via launchd (direct node command) |
❌ Hangs silently |
| OpenClaw gateway via launchd (bash wrapper that exec's node) |
❌ Hangs silently |
OpenClaw gateway via launchd with CI=true NO_COLOR=1 |
❌ Hangs silently |
OpenClaw gateway via launchd with /dev/null stdout/stderr |
❌ Hangs silently |
Conclusion: The hang is specific to OpenClaw's gateway code when running as a direct child of launchd (born via xpcproxy). It is not caused by environment variables, file descriptors, working directory, stdin, or the log paths.
Stack Trace (from /usr/bin/sample)
Call graph:
775 Thread_39985919 DispatchQueue_1: com.apple.main-thread (serial)
+ 775 start (in dyld)
+ 775 node::Start(int, char**)
+ 775 node::NodeMainInstance::Run()
+ 775 node::LoadEnvironment(...)
+ 775 node::StartExecution(...)
+ 775 node::InternalCallbackScope::Close()
+ 775 v8::Function::Call(...)
+ 775 v8::internal::MicrotaskQueue::RunMicrotasks(...)
+ 775 Builtins_PromiseFulfillReactionJob
+ 775 Builtins_AsyncFunctionAwaitResolveClosure
+ 775 node::loader::ModuleWrap::Evaluate(...)
+ 775 v8::Module::Evaluate(...)
The process is stuck in module evaluation — an async function is awaiting a promise that never resolves.
Additional Context
Related issues
Separate issue: external volume log paths cause exit code 78
When ~/.openclaw is a symlink to an external volume (e.g., /Volumes/SamsungX5/_openclaw), the StandardOutPath/StandardErrorPath in the plist resolve to the external volume. xpcproxy fails to set up these paths and exits with code 78 (EX_CONFIG) before the gateway binary ever runs. Moving log paths to a local directory (~/Library/Logs/openclaw/) fixes the exit code 78, but the gateway still hangs as described above.
Workaround
Starting the gateway manually (not via launchd) works reliably:
nohup openclaw gateway --port 18789 </dev/null >/dev/null 2>&1 &
We use an external watchdog (PM2-managed) that detects the gateway being down and restarts it via subprocess.Popen with start_new_session=True. This works but bypasses launchd entirely, meaning openclaw gateway start/stop/restart CLI commands don't work and the gateway won't auto-start on reboot without the watchdog.
Summary
When the gateway is started by macOS launchd (via LaunchAgent), the Node.js process loads but hangs forever during module evaluation — never binding a port, producing any output, or responding. The exact same command, environment, and arguments work correctly when run manually (including via
nohup,env -i, detached processes, or any other non-launchd method).Environment
Steps to Reproduce
openclaw gateway install)launchctl bootout gui/$(id -u)/ai.openclaw.gatewaylaunchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/ai.openclaw.gateway.plistcurl http://127.0.0.1:18789/→ no responseps -p $(launchctl print gui/$(id -u)/ai.openclaw.gateway 2>&1 | grep pid | awk '{print $3}') -o pid,stat,etime→ process running for minuteslaunchctl print gui/$(id -u)/ai.openclaw.gateway→runs = 1, pid = XXXXX, last exit code = (never exited)Expected Behavior
Gateway starts and listens on port 18789 within ~5 seconds (as it does when run manually).
Actual Behavior
The Node.js process starts (PPID=1, managed by launchd) but:
/tmp/openclaw/openclaw-*.log)S(sleeping) state indefinitely/usr/bin/sample) shows it is stuck innode::loader::ModuleWrap::Evaluate— V8 is trying to evaluate a JavaScript module and never completesDetailed Investigation
We tested every variable systematically to isolate the cause:
nohup, redirected stdin/stdoutenv -i(bare environment matching plist)cwd=/(matching launchd)XPC_SERVICE_NAME=ai.openclaw.gatewaysetOPENCLAW_LAUNCHD_LABEL=ai.openclaw.gatewaysetCI=true NO_COLOR=1/dev/nullstdout/stderrConclusion: The hang is specific to OpenClaw's gateway code when running as a direct child of launchd (born via xpcproxy). It is not caused by environment variables, file descriptors, working directory, stdin, or the log paths.
Stack Trace (from
/usr/bin/sample)The process is stuck in module evaluation — an async function is awaiting a promise that never resolves.
Additional Context
Related issues
gateway restartsilently fails to re-register LaunchAgent on macOS #42775 —gateway restartsilently fails to re-register LaunchAgentSeparate issue: external volume log paths cause exit code 78
When
~/.openclawis a symlink to an external volume (e.g.,/Volumes/SamsungX5/_openclaw), the StandardOutPath/StandardErrorPath in the plist resolve to the external volume. xpcproxy fails to set up these paths and exits with code 78 (EX_CONFIG) before the gateway binary ever runs. Moving log paths to a local directory (~/Library/Logs/openclaw/) fixes the exit code 78, but the gateway still hangs as described above.Workaround
Starting the gateway manually (not via launchd) works reliably:
We use an external watchdog (PM2-managed) that detects the gateway being down and restarts it via
subprocess.Popenwithstart_new_session=True. This works but bypasses launchd entirely, meaningopenclaw gateway start/stop/restartCLI commands don't work and the gateway won't auto-start on reboot without the watchdog.