Skip to content

TUI processes peg 100% CPU each and survive terminal close, eating all RAM #75289

@wolfgang-voss

Description

@wolfgang-voss

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

  1. Start the gateway via LaunchAgent
  2. Open openclaw tui --session provisioning in a terminal tab
  3. Either: (a) close the terminal tab, or (b) gateway restarts (e.g., after brew upgrade or openclaw doctor --fix rewrites the plist)
  4. The openclaw-tui process does not exit. It continues running at 100% CPU with ~900 MB RSS
  5. Opening new terminal tabs and running openclaw tui creates additional TUI processes that also spin
  6. 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:

  1. 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.

  2. 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.

  3. 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.

  4. 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

pkill -9 openclaw-tui

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions