Merge pull request #36864 from maxmilian/fix/tui-reset-terminal-input-modes-on-exit
fix(cli): reset terminal input modes on TUI exit to stop focus/mouse leaks
This commit is contained in:
64
cli.py
64
cli.py
@ -872,6 +872,17 @@ _cleanup_done = False
|
||||
# Weak reference to the active AIAgent for memory provider shutdown at exit
|
||||
_active_agent_ref = None
|
||||
_deferred_agent_startup_done = False
|
||||
# Set True once the TUI's prompt_toolkit app starts (which enables focus
|
||||
# reporting + mouse tracking). Gates the on-exit terminal reset so non-TUI
|
||||
# one-shot CLI runs — which also register _run_cleanup via atexit — don't emit
|
||||
# escape codes for modes they never enabled (#36823).
|
||||
_tui_input_modes_active = False
|
||||
|
||||
|
||||
def _mark_tui_input_modes_active() -> None:
|
||||
"""Record that the TUI app started, so _run_cleanup resets input modes."""
|
||||
global _tui_input_modes_active
|
||||
_tui_input_modes_active = True
|
||||
|
||||
|
||||
def _prepare_deferred_agent_startup() -> None:
|
||||
@ -927,6 +938,12 @@ def _run_cleanup():
|
||||
return
|
||||
_cleanup_done = True
|
||||
|
||||
# Reset terminal input modes first, before the slower resource teardown
|
||||
# below (MCP / browser / memory shutdown can take seconds). On Ctrl+C the
|
||||
# user's terminal becomes usable immediately, and a later step raising
|
||||
# can't skip the reset (#36823). No-op unless the TUI actually ran.
|
||||
_reset_terminal_input_modes_on_exit()
|
||||
|
||||
try:
|
||||
_cleanup_all_terminals()
|
||||
except Exception:
|
||||
@ -972,6 +989,50 @@ def _run_cleanup():
|
||||
pass
|
||||
|
||||
|
||||
def _reset_terminal_input_modes_on_exit() -> None:
|
||||
"""Best-effort: disable focus reporting + mouse tracking on TUI exit so they
|
||||
don't leak into the next shell session sharing the tab.
|
||||
|
||||
prompt_toolkit restores these on a clean teardown, but Ctrl+C, SIGTERM /
|
||||
SIGHUP and crashes can bypass its unwind, leaving the modes 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
|
||||
(#36823). Called from ``_run_cleanup`` (atexit-registered + invoked on the
|
||||
normal / EOF / interrupt exit paths) this covers normal quit, Ctrl+C and
|
||||
SIGTERM/SIGHUP. ``kill -9`` is uncatchable, and the kanban worker's
|
||||
``os._exit(0)`` path bypasses ``atexit``; neither runs this — but both are
|
||||
non-TTY / non-TUI, so there is nothing to reset there.
|
||||
|
||||
Gated on ``_tui_input_modes_active`` so one-shot non-TUI CLI runs (which
|
||||
share ``_run_cleanup`` via ``atexit``) never emit these codes. Writes to the
|
||||
controlling terminal directly: by exit, prompt_toolkit's own output is torn
|
||||
down, so ``sys.stdout`` is the real fd; falls back to ``/dev/tty`` when
|
||||
stdout is redirected away from the terminal.
|
||||
"""
|
||||
global _tui_input_modes_active
|
||||
if not _tui_input_modes_active:
|
||||
return
|
||||
# About to disable the modes — clear the flag so a re-armed _run_cleanup (or
|
||||
# a long-lived process that reuses it) doesn't re-emit them.
|
||||
_tui_input_modes_active = False
|
||||
# Prefer stdout when it's the terminal; otherwise the TUI may have driven
|
||||
# /dev/tty while stdout was redirected — reset there instead of nowhere.
|
||||
try:
|
||||
stream = sys.stdout
|
||||
if stream is not None and stream.isatty():
|
||||
stream.write(_TERMINAL_INPUT_MODE_RESET_SEQ)
|
||||
stream.flush()
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
with open("/dev/tty", "w", encoding="ascii") as tty:
|
||||
tty.write(_TERMINAL_INPUT_MODE_RESET_SEQ)
|
||||
tty.flush()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Git Worktree Isolation (#652)
|
||||
# =============================================================================
|
||||
@ -15135,6 +15196,9 @@ class HermesCLI:
|
||||
pass # No running loop -- nothing to patch
|
||||
except Exception:
|
||||
pass
|
||||
# The app enables focus reporting + mouse tracking; record that
|
||||
# so _run_cleanup resets them on exit (#36823).
|
||||
_mark_tui_input_modes_active()
|
||||
app.run()
|
||||
except (EOFError, KeyboardInterrupt, BrokenPipeError):
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user