Skip to content

fix(cli): recover terminal state after interrupt to prevent raw control sequence freeze#34064

Open
zccyman wants to merge 1 commit into
NousResearch:mainfrom
atyou2happy:fix/33271-terminal-raw-control-sequences
Open

fix(cli): recover terminal state after interrupt to prevent raw control sequence freeze#34064
zccyman wants to merge 1 commit into
NousResearch:mainfrom
atyou2happy:fix/33271-terminal-raw-control-sequences

Conversation

@zccyman

@zccyman zccyman commented May 28, 2026

Copy link
Copy Markdown
Contributor

Summary

When the agent is interrupted during processing (busy_input_mode=interrupt, the default), the terminal can become completely unresponsive. Raw CSI 6n cursor position report responses leak as literal text (^[[19;1R), and no further keyboard input is accepted.

Closes #33271

Problem

The process_loop daemon thread's finally block (after chat() returns) only called app.invalidate() to refresh the status line. When an interrupt occurred:

  1. prompt_toolkit may have emitted a CSI 6n cursor position query
  2. The interrupt killed the agent thread before the response was consumed
  3. The CPR response arrived on stdin but the VT100 input parser was in a broken state
  4. The response leaked to stdout as literal text, and the parser stalled — accepting no further keystrokes

The only recovery was killing the terminal tab/process.

Solution

In process_loop's finally block, after an interrupted turn (_last_turn_interrupted):

  1. flush_stdin() — drain stray escape-sequence bytes from the OS input buffer using termios.tcflush(TCIFLUSH), preventing them from corrupting the next input cycle
  2. _force_full_redraw() — reset prompt_toolkit's renderer cache and force a clean repaint, recovering from any cursor/screen state drift

Both operations are wrapped in try/except and are no-ops when stdin is not a TTY.

Files Changed

File Change
cli.py +16 lines: post-interrupt recovery in process_loop finally block
tests/cli/test_terminal_interrupt_recovery.py +127 lines: 6 regression tests

Test Results

tests/cli/test_terminal_interrupt_recovery.py::TestPostInterruptTerminalRecovery::test_no_recovery_when_turn_completes_normally PASSED
tests/cli/test_terminal_interrupt_recovery.py::TestPostInterruptTerminalRecovery::test_recovery_after_interrupt PASSED
tests/cli/test_terminal_interrupt_recovery.py::TestPostInterruptTerminalRecovery::test_flush_stdin_called_after_interrupt PASSED
tests/cli/test_terminal_interrupt_recovery.py::TestPostInterruptTerminalRecovery::test_flush_stdin_failure_does_not_prevent_redraw PASSED
tests/cli/test_terminal_interrupt_recovery.py::TestPostInterruptTerminalRecovery::test_agent_running_cleared_on_normal_exit PASSED
tests/cli/test_terminal_interrupt_recovery.py::TestPostInterruptTerminalRecovery::test_agent_running_cleared_on_interrupt PASSED

6 passed in 0.88s

Existing tests/cli/test_cli_force_redraw.py: 9 passed (zero regression)
Full tests/cli/: 673 passed, 1 pre-existing failure (unrelated)

Design Decisions

  1. Condition on _last_turn_interrupted — Only trigger recovery after an actual interrupt, not on every normal turn completion. This avoids unnecessary screen flicker on the common path.

  2. flush_stdin before _force_full_redraw — Drain stray bytes first so they don't arrive during the redraw and corrupt it. Order matters.

  3. Graceful degradation — If flush_stdin fails (no TTY, non-POSIX), we still attempt the full redraw. Both operations are independently safe.

…ol sequence freeze

When the agent is interrupted during processing, prompt_toolkit's
renderer and VT100 input parser can be left in an inconsistent state.
CSI 6n cursor position report responses leak as literal text
(^[[19;1R) and the terminal stops accepting keyboard input.

Fix: in process_loop's finally block, after an interrupted turn:
- flush_stdin() to drain stray escape bytes from the OS input buffer
- _force_full_redraw() to reset prompt_toolkit's renderer cache

Closes NousResearch#33271
@alt-glitch alt-glitch added type/bug Something isn't working P1 High — major feature broken, no workaround comp/cli CLI entry point, hermes_cli/, setup wizard labels May 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard P1 High — major feature broken, no workaround type/bug Something isn't working

Projects

None yet

2 participants