Skip to content

[Bug]: macOS app leaks orphaned SSH tunnel processes; PortGuardian never reaps them and they squat the preferred local port #100477

Description

@steipete

Bug type

Behavior bug (incorrect output/state without crash)

Beta release blocker

No

Summary

RemotePortTunnel spawns ssh -N -L <local>:127.0.0.1:18789 child processes, but cleanup only happens via terminate()/deinit in the owning app process. When the app crashes, is force-killed, or a dev build is stopped from Xcode, the ssh child reparents to launchd and keeps the forward alive indefinitely. PortGuardian.sweep never reaps them: it only inspects the canonical gateway port (GatewayEnvironment.gatewayPort()), and in remote mode it explicitly refuses to kill listeners on that port. The recorded pids in port-guard.json are written but never used to reap orphans.

Live evidence on one dev machine: 10 orphaned ssh -N -L tunnels to the same remote host (oldest from 2 days prior), including one squatting the preferred local port 18789. Because the preferred port is taken, every new tunnel falls back to a random ephemeral port (allowRandomLocalPort: true), which in turn makes the Dashboard WebView's baked-in endpoint go stale on every tunnel restart (see companion issue).

Steps to reproduce

  1. Remote (SSH) mode; let the app create a control tunnel.
  2. kill -9 the OpenClaw app (or stop a dev build from Xcode).
  3. ps aux | grep 'ssh -o BatchMode' → tunnel still running, parent launchd.
  4. Relaunch the app repeatedly; tunnels accumulate, and the preferred local port stays occupied by a dead session's tunnel.

Expected behavior

On startup (and on PortGuardian.sweep), reap recorded tunnel processes that no longer have a living owner — e.g. kill pids from port-guard.json whose command matches the app's ssh tunnel signature and whose parent is launchd (orphaned). Tunnels owned by other live OpenClaw instances must be left alone.

Actual behavior

Orphans accumulate forever, hold SSH connections to the remote host, and permanently occupy the preferred local port, forcing ephemeral-port drift.

OpenClaw version

main @ 5fdaa04

Operating system

macOS 26.5 (Apple Silicon)

Install method

git checkout (dev build)

Model

n/a

Provider / routing chain

n/a

Additional provider/model setup details

n/a

Logs

ps snapshot showed 10 ssh -o BatchMode=yes ... -N -L <port>:127.0.0.1:18789 -- steipete@<host> processes with ppid 1; only one belonged to the running app.

Screenshots, recordings, and evidence

  • Spawn + record: apps/macos/Sources/OpenClaw/RemotePortTunnel.swift (create, PortGuardian.shared.record).
  • Sweep never covers tunnel ports and skips remote mode: apps/macos/Sources/OpenClaw/PortGuardian.swift (sweep).
  • Random-port fallback when preferred port is occupied: RemotePortTunnel.findPort / RemoteTunnelManager.ensureControlTunnel.

Impact and severity

Resource leak (ssh processes + remote connections) and root cause of tunnel-port instability that breaks open Dashboard windows in remote mode (#100476; web-side UX: #100475). Medium.

Additional information

Note for the fix: sweep must not assume "any ssh -L to gateway port is ours to kill" — match against recorded pids from port-guard.json plus orphaned-parent check, since several OpenClaw instances (prod + dev) can legitimately run tunnels concurrently on one machine.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High-priority user-facing bug, regression, or broken workflow.bugSomething isn't workingclawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:otherThis issue has meaningful maintainer-visible impact outside the owned taxonomy.impact:ux-frictionUser-facing flow adds avoidable confusion or support burden without fully blocking progress.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.maintainerMaintainer-authored PRmaturity:stableIssue affects a taxonomy feature currently scored M4/M5.

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions