Summary
openclaw-tui processes spin at 100% CPU per process and do not exit when the controlling terminal closes or when the gateway restarts. On a 16 GB Mac mini running the gateway as a LaunchAgent, this repeatedly causes the machine to run out of RAM and swap-thrash until macOS kills processes or the machine becomes unresponsive.
The TUI processes also resist kill (SIGTERM) and require kill -9 (SIGKILL) to terminate.
Environment
- OpenClaw:
2026.4.29 (a448042)
- Node:
v25.6.1
- OS: macOS Darwin 25.3.0 (Mac mini, Apple Silicon, 16 GB RAM)
- Gateway mode: local loopback LaunchAgent, port 18789
- TUI invocation:
openclaw tui --session <name>
Reproduction
- Start the gateway via LaunchAgent
- Open
openclaw tui --session provisioning in a terminal tab
- Either: (a) close the terminal tab, or (b) gateway restarts (e.g., after
brew upgrade or openclaw doctor --fix rewrites the plist)
- The
openclaw-tui process does not exit. It continues running at 100% CPU with ~900 MB RSS
- Opening new terminal tabs and running
openclaw tui creates additional TUI processes that also spin
- Within minutes, 3-5 orphaned TUI processes consume 3-5 GB RAM and 300-500% CPU
Observed behavior
PID PROCESS RAM CPU
5302 openclaw-tui 1.1 GB 98%
4728 openclaw-tui 1.0 GB 99%
5696 openclaw-tui 810 MB 74%
4909 openclaw-gateway 1.1 GB 2%
kill <pid> (SIGTERM) does not terminate TUI processes
kill -9 <pid> is required
- Machine reaches 15/16 GB RAM used, 77 MB free, then swap-thrashes
- Gateway itself is fine (2% CPU, connectivity probe passes)
- This has happened multiple times across sessions
Root cause analysis (partial)
Code inspection of tui-DN9rW-QU.js and pi-tui shows:
-
No SIGHUP handler. The TUI registers handlers for SIGINT and SIGTERM (lines 4626-4627) but not SIGHUP. When a terminal tab closes, macOS sends SIGHUP. Node.js default SIGHUP behavior (exit) can be suppressed if any other code path registers a SIGHUP listener. attachChildProcessBridge in child-process-bridge-BMx3So6b.js temporarily registers one, which may leave a window where SIGHUP is swallowed.
-
SIGTERM handler calls requestExit() but the exit path may hang. requestExit() calls client.stop() then drainAndStopTuiSafely(tui), which calls tui.stop(). If tui.stop() tries to write to a dead stdout (terminal already closed), this could block or fail silently, preventing the process from actually exiting.
-
Loader animation keeps firing. The Loader component (pi-tui/dist/components/loader.js) runs a setInterval at 80ms that calls requestRender() on every tick. Each render writes to process.stdout. When stdout is a dead pipe (orphaned process), these writes may fail in a way that triggers immediate re-rendering rather than backing off.
-
The gateway client reconnect loop is properly throttled (exponential backoff 1s to 30s in client-CLNzli6Y.js:706-721), so the spin is not from reconnection attempts.
The exact mechanism causing 100% CPU in orphaned processes could not be fully pinned down via static analysis. Profiling a live orphaned process with --prof or --cpu-prof would identify the hot function.
Expected behavior
- TUI should exit when its controlling terminal closes (handle SIGHUP)
- TUI should exit cleanly on SIGTERM without requiring SIGKILL
- TUI should not spin at 100% CPU when disconnected from the gateway
- TUI should not accumulate multiple zombie processes across gateway restarts
Workaround
Summary
openclaw-tuiprocesses spin at 100% CPU per process and do not exit when the controlling terminal closes or when the gateway restarts. On a 16 GB Mac mini running the gateway as a LaunchAgent, this repeatedly causes the machine to run out of RAM and swap-thrash until macOS kills processes or the machine becomes unresponsive.The TUI processes also resist
kill(SIGTERM) and requirekill -9(SIGKILL) to terminate.Environment
2026.4.29 (a448042)v25.6.1openclaw tui --session <name>Reproduction
openclaw tui --session provisioningin a terminal tabbrew upgradeoropenclaw doctor --fixrewrites the plist)openclaw-tuiprocess does not exit. It continues running at 100% CPU with ~900 MB RSSopenclaw tuicreates additional TUI processes that also spinObserved behavior
kill <pid>(SIGTERM) does not terminate TUI processeskill -9 <pid>is requiredRoot cause analysis (partial)
Code inspection of
tui-DN9rW-QU.jsandpi-tuishows:No SIGHUP handler. The TUI registers handlers for SIGINT and SIGTERM (lines 4626-4627) but not SIGHUP. When a terminal tab closes, macOS sends SIGHUP. Node.js default SIGHUP behavior (exit) can be suppressed if any other code path registers a SIGHUP listener.
attachChildProcessBridgeinchild-process-bridge-BMx3So6b.jstemporarily registers one, which may leave a window where SIGHUP is swallowed.SIGTERM handler calls
requestExit()but the exit path may hang.requestExit()callsclient.stop()thendrainAndStopTuiSafely(tui), which callstui.stop(). Iftui.stop()tries to write to a dead stdout (terminal already closed), this could block or fail silently, preventing the process from actually exiting.Loader animation keeps firing. The
Loadercomponent (pi-tui/dist/components/loader.js) runs asetIntervalat 80ms that callsrequestRender()on every tick. Each render writes toprocess.stdout. When stdout is a dead pipe (orphaned process), these writes may fail in a way that triggers immediate re-rendering rather than backing off.The gateway client reconnect loop is properly throttled (exponential backoff 1s to 30s in
client-CLNzli6Y.js:706-721), so the spin is not from reconnection attempts.The exact mechanism causing 100% CPU in orphaned processes could not be fully pinned down via static analysis. Profiling a live orphaned process with
--profor--cpu-profwould identify the hot function.Expected behavior
Workaround