Skip to content

macOS: per-port lsof port-health polling can saturate launchservicesd and trigger a WindowServer watchdog reboot #90548

Description

@jmmas123

Summary

On macOS, the gateway's port-health checks spawn lsof once per inspected port on every poll cycle. On a busy machine this steady stream of short-lived process launches can saturate macOS's launch pipeline (syspolicyd Gatekeeper assessment + launchservicesd check-in) badly enough that launchservicesd hits its 512 dispatch-thread hard limit. When that happens, WindowServer blocks on a synchronous LaunchServices XPC call, goes unresponsive, and the OS watchdog (watchdogd) kills it — which tears down the graphics session and looks to the user like a freeze + reboot.

I traced a hard "froze and rebooted" event on my Mac to exactly this.

Environment

  • macOS 26.5 (25F71), Apple Silicon (Mac15,10)
  • openclaw/clawdbot gateway installed as a KeepAlive LaunchAgent (com.clawdbot.gateway), node, port 18789
  • Package version 2026.1.23-1 (current npm latest 2026.1.24-3)
  • Busy host: ~800 processes, 2 VMs, VPN, an IDS, several Electron apps (this matters — see caveat)

What I observed

dist/infra/ports-inspect.js runs, per port:

runCommandSafe([lsof, "-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-FpFcn"])

In the macOS unified log, the gateway spawned lsof like clockwork — one invocation every ~2.2 seconds, sustained (≈27/min, consistent with iterating the gateway's service ports across the ~60s poll interval). Each spawn forces a full launch pipeline pass:

  • syspolicyd Gatekeeper/notarization assessment (I measured ~41 assessments/sec across the system during the incident window)
  • launchservicesd CHECKIN + code-signing verification

Root-cause chain (from Apple's own crash captures)

per-port lsof poll (continuous)
  → syspolicyd + launchservicesd back up
  → launchservicesd hits the 512 dispatch-thread HARD LIMIT
  → WindowServer's synchronous LaunchServices call blocks (turnstile wait on launchservicesd)
  → WindowServer main thread unresponsive 40s
  → watchdogd kills WindowServer  → graphics session torn down (apparent reboot)

The WindowServer-*.userspace_watchdog_timeout.spin report states verbatim:

Processes reached dispatch thread hard limit (512): launchservicesd

and WindowServer's main-thread stack at the time was:

WindowServer → SkyLight → LaunchServices → libxpc → mach_msg
  *blocked by turnstile waiting for launchservicesd

Caveat / honesty

My host was heavily loaded, which lowered the threshold — openclaw is unlikely to do this on an idle machine. But a per-minute, per-port lsof sweep is a lot of process-launch pressure on macOS specifically (every exec hits Gatekeeper + LaunchServices), so it's a real contributing factor worth hardening.

Suggested mitigations

  1. One lsof per cycle, not one per port. A single lsof -nP -iTCP -sTCP:LISTEN lists all listening TCP sockets at once; parse it for the ports of interest. This collapses ~27 spawns/cycle to 1.
  2. Lengthen / back off the poll interval when the gateway is healthy and idle, or move to event-driven checks.
  3. Prefer a non-spawning probe where possible (e.g., attempt a bind/connect via node:net) and reserve lsof for the "who owns this port?" detail path only when a conflict is detected.
  4. Related precedent: Linux: Port diagnostics should fall back to ss when lsof is missing #14083 added a ss fallback on Linux; the macOS path could similarly minimize lsof fan-out.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal backlog priority with limited blast radius.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:needs-live-reproClawSweeper needs live local, crabbox, or manual validation to confirm this issue.clawsweeper:needs-maintainer-reviewClawSweeper marked this issue as needing maintainer review before automation.clawsweeper:needs-product-decisionClawSweeper marked this issue as needing a product or behavior decision.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.impact:crash-loopCrash, hang, restart loop, or process-level availability failure.issue-rating: 🐚 platinum hermitGood issue quality with a plausible reproduction path needing some confirmation.

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions