-
Notifications
You must be signed in to change notification settings - Fork 494
bug: missing SIGTSTP handler causes mouse garbling when process is externally suspended #906
Description
Description
When a process using @opentui/core receives SIGTSTP from an external source (e.g., code-server terminal management, job control, kill -TSTP), the process is suspended by the kernel without any terminal cleanup. Mouse tracking remains enabled, causing mouse events to appear as garbled escape sequences in the shell.
Root Cause
opentui's exitSignals list includes SIGINT, SIGTERM, SIGQUIT, SIGABRT, SIGHUP, SIGBREAK, SIGPIPE, SIGBUS, SIGFPE — but no SIGTSTP.
There is no process.on("SIGTSTP") handler anywhere in the renderer. When SIGTSTP arrives:
- Mouse tracking (
\x1b[?1000h,\x1b[?1003h,\x1b[?1006h) stays enabled - Kitty keyboard protocol stays enabled
- Raw mode stays on (until the shell overrides)
- The shell receives mouse events as raw escape sequences → garbled text
The Correct Pattern Already Exists
The manual suspend() method does this correctly:
suspend() {
this._suspendedMouseEnabled = this._useMouse
this.disableMouse() // 1. disable mouse
this.removeExitListeners()
this.stdinParser?.reset()
this.stdin.removeListener("data", this.stdinListener)
this.lib.suspendRenderer(...) // 2. native suspend
this.stdin.setRawMode(false) // 3. raw mode off
this.stdin.pause()
}But this is only called from consumer code (e.g., a keybind handler). An external SIGTSTP bypasses it entirely.
Proposed Fix
Register SIGTSTP/SIGCONT handlers in the renderer that call suspend()/resume():
private sigtstpHandler = () => {
this.suspend()
// Remove handler to allow default behavior (suspend)
process.removeListener("SIGTSTP", this.sigtstpHandler)
// Re-raise to actually suspend the process
process.kill(process.pid, "SIGTSTP")
}
private sigcontHandler = () => {
// Re-register SIGTSTP handler
process.on("SIGTSTP", this.sigtstpHandler)
this.resume()
}Register in setupTerminal(), remove in cleanupBeforeDestroy().
Note: In Node/Bun, registering a SIGTSTP handler overrides the default behavior (immediate suspend). The handler must remove itself and re-raise SIGTSTP to actually suspend. The SIGCONT handler re-registers it.
Reproduction
- Run any opentui TUI application with mouse tracking enabled
- In another terminal:
kill -TSTP $(pgrep -f <app>) - Move the mouse in the shell
- Garbled escape sequences appear:
35;89;19M35;84;20M35...
Observed in opencode running in code-server (VS Code web terminal) where the terminal management layer can send SIGTSTP during inactivity.
Related
- bug: mouse escape sequences garbled after renderer destroy — cleanupBeforeDestroy() disables raw mode before mouse tracking #904 / fix(renderer): disable mouse tracking before raw mode in destroy path #905 — cleanupBeforeDestroy ordering fix (different bug, same symptom)