Bug Description
Version: 0.13.0
Platform: Linux
The destructive-slash confirmation prompt added to cli.py (_confirm_destructive_slash, around line 8362) renders correctly but its input intercept never fires. Typing the answer (1, 2, or 3) sends that character to the agent as a chat message instead of resolving the prompt.
Steps to Reproduce
- Start Hermes CLI in TUI mode (hermes chat).
- Type /clear (or /new, /reset, /undo).
- Confirmation prompt prints:
⚠ /clear — destroys conversation state
This clears the screen and starts a new session.
The current conversation history will be discarded.
[1] Approve Once — proceed this time only
[2] Always Approve — proceed and silence this prompt permanently
[3] Cancel — keep current conversation
- Type 2 and press Enter.
Expected Behavior
prompt resolves, "Always Approve" path runs, approvals.destructive_slash_confirm is persisted to false.
Actual Behavior
the 2 is delivered to the agent as a user message ("2"). The prompt remains unresolved. The destructive command never runs and never cancels — it's just abandoned.
Affected Component
CLI (interactive chat)
Messaging Platform (if gateway-related)
No response
Debug Report
N/A, contains identifiable personal information due to machine setup
Operating System
Pop!OS 24.04
Python Version
3.11.15
Hermes Version
0.13.0
Additional Logs / Traceback (optional)
Root Cause Analysis (optional)
Most likely cause:
_prompt_text_input (cli.py:5860) calls Python's built-in input() from inside prompt_toolkit.application.run_in_terminal:
def _prompt_text_input(self, prompt_text: str) -> str | None:
result = [None]
def _ask():
try:
result[0] = input(prompt_text).strip() or None
except (KeyboardInterrupt, EOFError):
pass
if self._app:
from prompt_toolkit.application import run_in_terminal
was_visible = self._status_bar_visible
self._status_bar_visible = False
self._app.invalidate()
try:
run_in_terminal(_ask)
finally:
self._status_bar_visible = was_visible
self._app.invalidate()
else:
_ask()
return result[0]
When the TUI Application is active, run_in_terminal is supposed to suspend the app's screen and hand stdin back to the wrapped callable. In this code path that handoff doesn't appear to happen — the prompt prints, but keystrokes continue to be consumed by prompt_toolkit's main input handler and end up in _pending_input, which process_loop then forwards to the agent as a normal user message. The result [None] is never populated, the prompt returns None, and _confirm_destructive_slash treats that as "cancelled (no input)", except the cancel message is also never printed because the function is sitting on input() that will never return.
May be related — same session as a broken-prompt repro shows these in ~/.hermes/logs/errors.log:
WARNING cli: process_loop unhandled error (msg may be lost):
There is no current event loop in thread 'Thread-3 (process_loop)'.
process_loop is the thread that drains _pending_input, so an asyncio-context bug there could plausibly interfere with run_in_terminal's stdin handoff. Not confirmed, just a correlation worth checking.
Proposed Fix (optional)
Replace the built-in input() in _prompt_text_input with prompt_toolkit's native prompt(), which cooperates with the active Application instead of competing for stdin:
def _prompt_text_input(self, prompt_text: str) -> str | None:
if self._app:
from prompt_toolkit.application import run_in_terminal
from prompt_toolkit.shortcuts import prompt as pt_prompt
result = [None]
def _ask():
try:
raw = pt_prompt(prompt_text)
result[0] = raw.strip() or None
except (KeyboardInterrupt, EOFError):
pass
was_visible = self._status_bar_visible
self._status_bar_visible = False
self._app.invalidate()
try:
run_in_terminal(_ask)
finally:
self._status_bar_visible = was_visible
self._app.invalidate()
return result[0]
# Non-TUI fallback unchanged
try:
return input(prompt_text).strip() or None
except (KeyboardInterrupt, EOFError):
return None
A more invasive but cleaner alternative: render the choice as a prompt_toolkit modal, similar to the /model picker at cli.py:5884 (_open_model_picker). That reuses the existing input-capture machinery and gets you arrow-key navigation for free, but it's a bigger change.
Workaround for affected users
Add to ~/.hermes/config.yaml:
approvals:
destructive_slash_confirm: false
This bypasses the prompt entirely (cli.py:8391–8392). Confirmed working on 0.13.0.
Are you willing to submit a PR for this?
Bug Description
Version: 0.13.0
Platform: Linux
The destructive-slash confirmation prompt added to cli.py (_confirm_destructive_slash, around line 8362) renders correctly but its input intercept never fires. Typing the answer (1, 2, or 3) sends that character to the agent as a chat message instead of resolving the prompt.
Steps to Reproduce
Expected Behavior
prompt resolves, "Always Approve" path runs, approvals.destructive_slash_confirm is persisted to false.
Actual Behavior
the 2 is delivered to the agent as a user message ("2"). The prompt remains unresolved. The destructive command never runs and never cancels — it's just abandoned.
Affected Component
CLI (interactive chat)
Messaging Platform (if gateway-related)
No response
Debug Report
Operating System
Pop!OS 24.04
Python Version
3.11.15
Hermes Version
0.13.0
Additional Logs / Traceback (optional)
Root Cause Analysis (optional)
Most likely cause:
_prompt_text_input (cli.py:5860) calls Python's built-in input() from inside prompt_toolkit.application.run_in_terminal:
When the TUI Application is active, run_in_terminal is supposed to suspend the app's screen and hand stdin back to the wrapped callable. In this code path that handoff doesn't appear to happen — the prompt prints, but keystrokes continue to be consumed by prompt_toolkit's main input handler and end up in _pending_input, which process_loop then forwards to the agent as a normal user message. The result [None] is never populated, the prompt returns None, and _confirm_destructive_slash treats that as "cancelled (no input)", except the cancel message is also never printed because the function is sitting on input() that will never return.
May be related — same session as a broken-prompt repro shows these in ~/.hermes/logs/errors.log:
WARNING cli: process_loop unhandled error (msg may be lost):
There is no current event loop in thread 'Thread-3 (process_loop)'.
process_loop is the thread that drains _pending_input, so an asyncio-context bug there could plausibly interfere with run_in_terminal's stdin handoff. Not confirmed, just a correlation worth checking.
Proposed Fix (optional)
Replace the built-in input() in _prompt_text_input with prompt_toolkit's native prompt(), which cooperates with the active Application instead of competing for stdin:
A more invasive but cleaner alternative: render the choice as a prompt_toolkit modal, similar to the /model picker at cli.py:5884 (_open_model_picker). That reuses the existing input-capture machinery and gets you arrow-key navigation for free, but it's a bigger change.
Workaround for affected users
Add to ~/.hermes/config.yaml:
This bypasses the prompt entirely (cli.py:8391–8392). Confirmed working on 0.13.0.
Are you willing to submit a PR for this?