Skip to content

Gateway hangs silently on startup when launched by launchd (macOS) #46153

Description

@frankr

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

  1. Install gateway as LaunchAgent (openclaw gateway install)
  2. Bootout existing stuck service: launchctl bootout gui/$(id -u)/ai.openclaw.gateway
  3. Bootstrap fresh: launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/ai.openclaw.gateway.plist
  4. Wait 30+ seconds
  5. Check: curl http://127.0.0.1:18789/ → no response
  6. 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
  7. Check launchd state: launchctl print gui/$(id -u)/ai.openclaw.gatewayruns = 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High-priority user-facing bug, regression, or broken workflow.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:queueable-fixClawSweeper marked this issue as an existing queue_fix_pr work candidate.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions