fix(cli): reset terminal input modes on TUI exit to stop focus/mouse leaks - #37458
Closed
teknium1 wants to merge 1 commit into
Closed
fix(cli): reset terminal input modes on TUI exit to stop focus/mouse leaks#37458teknium1 wants to merge 1 commit into
teknium1 wants to merge 1 commit into
Conversation
…leaks When the TUI exits via Ctrl+C, SIGTERM/SIGHUP, or a crash, prompt_toolkit's teardown can be bypassed, leaving DEC 1004 (focus reporting) and 1000/1002/1003 (mouse tracking) enabled. The terminal then emits raw ESC[I/ESC[O focus events and fragmented SGR mouse reports as visible text in whatever runs next in the same tab. _run_cleanup() — the once-only cleanup that runs on every catchable exit path (atexit-registered + called on the normal/EOF/interrupt exit) — now emits _TERMINAL_INPUT_MODE_RESET_SEQ (the same disable sequence the in-session leak recovery already uses) as its FIRST step, so the terminal is usable immediately on Ctrl+C and a later teardown step raising can't skip it. The reset is gated on a new _tui_input_modes_active flag (set right before app.run(), cleared once the modes are disabled) so non-TUI one-shot CLI runs — which share _run_cleanup via atexit — don't emit codes for modes they never enabled. Writes to sys.stdout when it's the terminal, else falls back to /dev/tty. SIGKILL is uncatchable and the kanban worker's os._exit(0) bypasses atexit, but both are non-TTY/non-TUI so there is nothing to reset there. Adds tests/cli/test_tui_terminal_reset_on_exit.py (9): emits on a TTY when the TUI ran, no-ops when the TUI never ran, /dev/tty fallback when stdout is redirected, no-op when neither is available, swallows stdout errors, flag set and cleared, and wired into _run_cleanup as the first step even when a later step raises. Fixes #36823 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
🔎 Lint report:
|
Collaborator
|
Duplicate of #36864 (identical fix, cherry-picked from it per PR body). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Exiting the TUI no longer leaves focus-reporting + mouse tracking enabled in the terminal, so the next shell in that tab stops getting flooded with raw
ESC[I/ESC[Ofocus events and fragmented SGR mouse reports as visible text.Root cause:
prompt_toolkitrestores these DEC private modes on a clean teardown, but Ctrl+C, SIGTERM/SIGHUP, and crashes bypass its unwind, leaving DEC 1004 (focus) and 1000/1002/1003 (mouse) on. On main,_TERMINAL_INPUT_MODE_RESET_SEQwas only ever fired mid-session by the in-session leak-recovery path — never on exit.Changes
cli.py:_run_cleanup()now emits_TERMINAL_INPUT_MODE_RESET_SEQas its first step (before the slower MCP/browser/memory teardown), so the terminal is usable immediately on Ctrl+C and a later teardown raising can't skip the reset._tui_input_modes_activeflag (set right beforeapp.run(), cleared once reset) so non-TUI one-shot CLI runs — which share_run_cleanupviaatexit— never emit codes for modes they never enabled.sys.stdoutwhen it's the TTY, falls back to/dev/ttywhen stdout is redirected.tests/cli/test_tui_terminal_reset_on_exit.py: 9 tests.Validation
E2E (live imports): emits reset only when TUI ran; no-op otherwise; flag cleared after emit (idempotent); reset disables 1004/1006/1003.
Salvaged from #36864 by @maxmilian (commit cherry-picked, authorship preserved). Fixes #36823 — also addresses the exit-side of #29557.
Infographic