Bug Description
hermes 0.14.0
Title: Terminal becomes unresponsive with raw control sequences (e.g., ^[[19;1R) after interrupting a running agent with a new message
Version: Hermes CLI (latest)
Platform: macOS / Linux (observed on both)
Summary
When the Hermes CLI is in interactive mode and the user sends a new message while the agent is still processing the previous turn (e.g., while it is generating a response or executing tool calls), the terminal occasionally becomes completely unresponsive. Before freezing, a string like ^[[19;1R (or similar) appears on the screen. After that, no further keyboard input is accepted, and the user must force‑close the terminal window or kill the Hermes process.
This issue does not happen on every interruption – it is intermittent, but once triggered it reliably locks the terminal.
Steps to Reproduce
Start Hermes CLI in interactive mode:
bash
hermes chat
Send a message that will cause the agent to take several seconds to respond – for example, a request to perform a complex reasoning task, execute a long‑running terminal command, or generate a large amount of text.
Example:
text
Write a detailed 500‑word explanation of how TCP congestion control works.
While the agent is still working (the spinner is active and no response has been printed yet), type a new message – even a short one like "Actually, never mind" – and press Enter.
Observe the terminal:
Sometimes the interruption works cleanly (the agent stops, the new message is processed).
Other times, a string like ^[[19;1R (or ^[[27;1R, ^[[12;1R, etc.) appears on a new line.
After that, the terminal appears to accept no further keystrokes. You can type, but nothing is echoed, and pressing Enter does nothing.
The only recovery is to close the terminal tab/window or kill the Hermes process (e.g., killall hermes).
Expected Behavior
Interrupting a running agent should always leave the terminal in a usable state. The next prompt should appear correctly, and the user should be able to continue typing new messages.
Actual Behavior
Occasionally, the terminal enters a broken state where control sequences intended for internal use (cursor position reports) are displayed as literal text, and input becomes impossible.
Additional Observations
The exact numeric values in the control sequence vary (19;1R, 27;1R, etc.) – they appear to be terminal cursor row/column reports.
The issue is not reproducible on every interruption. It may happen after 5–10 successful interruptions, or sometimes on the first.
It occurs more frequently when the agent is interrupted during or immediately after tool execution (e.g., running a curl, git, or docker command) than during pure text generation.
Once the terminal is frozen, even Ctrl+C, Ctrl+D, or Ctrl+Z have no effect.
Technical Analysis (for the maintainers)
The visible symptom – a raw cursor position report (CSI 6n response) being printed as literal text – strongly indicates that the terminal has been left in a state where:
The application (Hermes) is no longer parsing incoming control sequences.
The terminal may have been switched to a mode (e.g., raw mode) and not restored.
Based on the code, likely causes include:
Improper terminal mode restoration after interruption
When AIAgent.interrupt() is called, it stops ongoing tool execution and background threads (e.g., background review, terminal subprocesses). Some of these components may have temporarily changed terminal attributes (e.g., using tty.setcbreak(), termios.tcsetattr(), or calling stty). If the interrupt occurs at the wrong moment, the restoration code may be skipped, leaving the terminal in raw mode.
Race condition with prompt_toolkit's cursor position query
prompt_toolkit internally sends \033[6n to determine the cursor position (e.g., after a resize or layout change). If the response from the terminal arrives after the input parser has been shut down or interrupted, that response can be written directly to stdout instead of being handled by the key binding system.
_query_osc11_background in cli.py
This function temporarily puts the terminal into cbreak mode to query the background color via OSC 11. If an interrupt arrives while this function is active, the terminal may never be restored to its normal state.
terminal_tool subprocesses
When the agent runs commands via the terminal tool, it may change terminal settings (e.g., for password prompts or interactive input). If the agent is interrupted while such a command is still running, the subprocess may be killed before it can restore the original settings, leaving the terminal in a broken state.
Suggested Fixes (Recommended Approach)
We suggest the following changes to make the interruption path robust:
Centralise terminal state restoration
In AIAgent.interrupt(), after propagating the interrupt to all threads and subagents, call a method that unconditionally resets the terminal to a sane state (e.g., stty sane or an equivalent termios reset). This should be done regardless of whether a tool was active.
Wrap all terminal‑mode‑changing operations
Functions like _query_osc11_background, and any use of tty.setcbreak() or termios, should be wrapped in a context manager that ensures restoration even if an interrupt occurs. The context manager should catch KeyboardInterrupt and SystemExit and restore settings before re‑raising.
Make prompt_toolkit input parsing more resilient
After an interrupt, force a full refresh of the prompt‑toolkit UI by calling app.invalidate() and possibly resetting the input parser state. In HermesCLI.run(), after the agent thread finishes (both normal exit and after interruption), call _force_full_redraw() to re‑render the prompt and re‑initialise the input handling.
Add a post‑interrupt sanity check
In the main chat loop, after the agent finishes (whether interrupted or not), check if the terminal is still in raw mode by attempting a small query/response. If it is broken, automatically reset it and re‑display the prompt.
Disable terminal‑changing features during agent runs
As a workaround, consider not using cursor position queries or background color detection while the agent is active. These queries are mostly needed for UI layout and could be deferred to idle moments.
Example Terminal Log (after freeze)
text
[... previous output ...]
┊ ❯ curl -sL "https://..." -o /tmp/example
^[[19;1R ◉_◉ pondering...
At this point, typing does nothing, and the only way out is to kill the terminal.
Workaround for Users
Until the issue is fixed, users can:
Avoid sending new messages while the agent is working (wait for the response to finish).
Use /new to start a fresh session if the terminal becomes unresponsive (though this requires killing the terminal first).
Run Hermes inside a terminal multiplexer (tmux) – it may still freeze the pane, but the outer terminal remains usable.
Please let me know if you need any additional information or a core dump. I'm happy to help test any patches.
Thank you for your excellent work on Hermes.
Steps to Reproduce
hermes 0.14.0
Expected Behavior
hermes 0.14.0
Actual Behavior
hermes 0.14.0
Affected Component
CLI (interactive chat)
Messaging Platform (if gateway-related)
No response
Debug Report
Operating System
hermes 0.14.0
Python Version
hermes 0.14.0
Hermes Version
hermes 0.14.0
Additional Logs / Traceback (optional)
Root Cause Analysis (optional)
No response
Proposed Fix (optional)
No response
Are you willing to submit a PR for this?
Bug Description
hermes 0.14.0
Title: Terminal becomes unresponsive with raw control sequences (e.g., ^[[19;1R) after interrupting a running agent with a new message
Version: Hermes CLI (latest)
Platform: macOS / Linux (observed on both)
Summary
When the Hermes CLI is in interactive mode and the user sends a new message while the agent is still processing the previous turn (e.g., while it is generating a response or executing tool calls), the terminal occasionally becomes completely unresponsive. Before freezing, a string like ^[[19;1R (or similar) appears on the screen. After that, no further keyboard input is accepted, and the user must force‑close the terminal window or kill the Hermes process.
This issue does not happen on every interruption – it is intermittent, but once triggered it reliably locks the terminal.
Steps to Reproduce
Start Hermes CLI in interactive mode:
bash
hermes chat
Send a message that will cause the agent to take several seconds to respond – for example, a request to perform a complex reasoning task, execute a long‑running terminal command, or generate a large amount of text.
Example:
text
Sometimes the interruption works cleanly (the agent stops, the new message is processed).
Other times, a string like ^[[19;1R (or ^[[27;1R, ^[[12;1R, etc.) appears on a new line.
After that, the terminal appears to accept no further keystrokes. You can type, but nothing is echoed, and pressing Enter does nothing.
The only recovery is to close the terminal tab/window or kill the Hermes process (e.g., killall hermes).
Expected Behavior
Interrupting a running agent should always leave the terminal in a usable state. The next prompt should appear correctly, and the user should be able to continue typing new messages.
Actual Behavior
Occasionally, the terminal enters a broken state where control sequences intended for internal use (cursor position reports) are displayed as literal text, and input becomes impossible.
Additional Observations
The exact numeric values in the control sequence vary (19;1R, 27;1R, etc.) – they appear to be terminal cursor row/column reports.
The issue is not reproducible on every interruption. It may happen after 5–10 successful interruptions, or sometimes on the first.
It occurs more frequently when the agent is interrupted during or immediately after tool execution (e.g., running a curl, git, or docker command) than during pure text generation.
Once the terminal is frozen, even Ctrl+C, Ctrl+D, or Ctrl+Z have no effect.
Technical Analysis (for the maintainers)
The visible symptom – a raw cursor position report (CSI 6n response) being printed as literal text – strongly indicates that the terminal has been left in a state where:
The application (Hermes) is no longer parsing incoming control sequences.
The terminal may have been switched to a mode (e.g., raw mode) and not restored.
Based on the code, likely causes include:
Improper terminal mode restoration after interruption
When AIAgent.interrupt() is called, it stops ongoing tool execution and background threads (e.g., background review, terminal subprocesses). Some of these components may have temporarily changed terminal attributes (e.g., using tty.setcbreak(), termios.tcsetattr(), or calling stty). If the interrupt occurs at the wrong moment, the restoration code may be skipped, leaving the terminal in raw mode.
Race condition with prompt_toolkit's cursor position query
prompt_toolkit internally sends \033[6n to determine the cursor position (e.g., after a resize or layout change). If the response from the terminal arrives after the input parser has been shut down or interrupted, that response can be written directly to stdout instead of being handled by the key binding system.
_query_osc11_background in cli.py
This function temporarily puts the terminal into cbreak mode to query the background color via OSC 11. If an interrupt arrives while this function is active, the terminal may never be restored to its normal state.
terminal_tool subprocesses
When the agent runs commands via the terminal tool, it may change terminal settings (e.g., for password prompts or interactive input). If the agent is interrupted while such a command is still running, the subprocess may be killed before it can restore the original settings, leaving the terminal in a broken state.
Suggested Fixes (Recommended Approach)
We suggest the following changes to make the interruption path robust:
Centralise terminal state restoration
In AIAgent.interrupt(), after propagating the interrupt to all threads and subagents, call a method that unconditionally resets the terminal to a sane state (e.g., stty sane or an equivalent termios reset). This should be done regardless of whether a tool was active.
Wrap all terminal‑mode‑changing operations
Functions like _query_osc11_background, and any use of tty.setcbreak() or termios, should be wrapped in a context manager that ensures restoration even if an interrupt occurs. The context manager should catch KeyboardInterrupt and SystemExit and restore settings before re‑raising.
Make prompt_toolkit input parsing more resilient
After an interrupt, force a full refresh of the prompt‑toolkit UI by calling app.invalidate() and possibly resetting the input parser state. In HermesCLI.run(), after the agent thread finishes (both normal exit and after interruption), call _force_full_redraw() to re‑render the prompt and re‑initialise the input handling.
Add a post‑interrupt sanity check
In the main chat loop, after the agent finishes (whether interrupted or not), check if the terminal is still in raw mode by attempting a small query/response. If it is broken, automatically reset it and re‑display the prompt.
Disable terminal‑changing features during agent runs
As a workaround, consider not using cursor position queries or background color detection while the agent is active. These queries are mostly needed for UI layout and could be deferred to idle moments.
Example Terminal Log (after freeze)
text
[... previous output ...]
┊ ❯ curl -sL "https://..." -o /tmp/example
^[[19;1R ◉_◉ pondering...
At this point, typing does nothing, and the only way out is to kill the terminal.
Workaround for Users
Until the issue is fixed, users can:
Avoid sending new messages while the agent is working (wait for the response to finish).
Use /new to start a fresh session if the terminal becomes unresponsive (though this requires killing the terminal first).
Run Hermes inside a terminal multiplexer (tmux) – it may still freeze the pane, but the outer terminal remains usable.
Please let me know if you need any additional information or a core dump. I'm happy to help test any patches.
Thank you for your excellent work on Hermes.
Steps to Reproduce
hermes 0.14.0
Expected Behavior
hermes 0.14.0
Actual Behavior
hermes 0.14.0
Affected Component
CLI (interactive chat)
Messaging Platform (if gateway-related)
No response
Debug Report
Operating System
hermes 0.14.0
Python Version
hermes 0.14.0
Hermes Version
hermes 0.14.0
Additional Logs / Traceback (optional)
Root Cause Analysis (optional)
No response
Proposed Fix (optional)
No response
Are you willing to submit a PR for this?