Skip to content

fix: approval prompt freeze — CLI thread-local + TUI blind-keystroke#13697

Merged
teknium1 merged 2 commits into
mainfrom
hermes/hermes-27c355e0
Apr 21, 2026
Merged

fix: approval prompt freeze — CLI thread-local + TUI blind-keystroke#13697
teknium1 merged 2 commits into
mainfrom
hermes/hermes-27c355e0

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Restores dangerous-command approval in both CLI and TUI — approval prompts now accept keystrokes instead of freezing the terminal for 60s.

Salvage of #13634 by @Societus (rebase-merged for authorship preservation) + regression-guard tests.

Root cause

62348cf (#13525) moved _approval_callback / _sudo_password_callback to threading.local() to fix GHSA-qg5c-hvr5-hjgr (ACP race). CLI registers callbacks in the main thread but the agent runs in a daemon thread spawned by chat()threading.local doesn't propagate, so _get_approval_callback() returned None in the agent thread and fell back to input(), which deadlocks inside prompt_toolkit.

TUI had an adjacent bug: useInputHandlers consumed keystrokes via return when isBlocked, but Ink's EventEmitter still delivered them to ApprovalPrompt — user saw a frozen overlay while blind keystrokes could silently approve dangerous commands session-wide or write to the permanent allowlist.

Changes

File Change
cli.py Register approval/sudo/secret callbacks inside run_agent() thread target; clear in finally
ui-tui/src/app/useInputHandlers.ts When approval/clarify/confirm overlay active, only intercept Ctrl+C — let arrow/number/Enter fall through
tests/cli/test_cli_approval_ui.py Two regression guards pinning the thread-local contract

Validation

  • tests/cli/test_cli_approval_ui.py tests/acp/test_approval_isolation.py tests/tools/test_command_guards.py — 35 passed
  • E2E: confirmed main-thread registration is invisible to child thread on current main, and child-thread registration is visible + cleared on finally
  • TUI: tsc --noEmit clean

Closes #13617, closes #13618.

Societus and others added 2 commits April 21, 2026 14:22
…ead-local callback invisible to agent

Two bugs that allow dangerous commands to execute without informed user consent.

TUI (Ink): useInputHandlers consumes the isBlocked return path, but Ink's
EventEmitter delivers keystrokes to ALL registered useInput listeners. The
ApprovalPrompt component receives arrow keys, number keys, and Enter even
though the overlay appears frozen. The user sees no visual feedback, but
keystrokes are processed — allowing blind approval, session-wide auto-approve
(choice "session"), or permanent allowlist writes (choice "always") without
the user knowing.

Discovered while replicating #13618 (TUI approval overlay freezes terminal).

Fix: in useInputHandlers, when overlay.approval/clarify/confirm is active,
only intercept Ctrl+C. All other keys pass through. This makes the overlay
visually responsive so the user can see what they are selecting.

CLI (prompt_toolkit): _callback_tls in terminal_tool.py is threading.local().
set_approval_callback() is called in the main thread during run(), but the
agent executes in a background thread. _get_approval_callback() returns None
in the agent thread, falling back to stdin input() which prompt_toolkit
blocks. The user sees the approval text but cannot respond — the terminal is
unusable until the 60s timeout expires with a default "deny".

Fix: set callbacks inside run_agent() (the thread target), matching the
pattern already used by acp_adapter/server.py. Clear on thread exit to avoid
stale references.

Closes #13618
Two unit tests that pin down the threading.local semantics the CLI freeze
fix (#13617 / #13618) relies on:

- main-thread registration must be invisible to child threads (documents
  the underlying bug — if this ever starts passing visible, ACP's
  GHSA-qg5c-hvr5-hjgr race has returned)
- child-thread registration must be visible from that same thread AND
  cleared by the finally block (documents the fix pattern used by
  cli.py's run_agent closure and acp_adapter/server.py)

Pairs with the fix in the preceding commit by @Societus.
@teknium1 teknium1 merged commit ef589b1 into main Apr 21, 2026
11 of 12 checks passed
@teknium1 teknium1 deleted the hermes/hermes-27c355e0 branch April 21, 2026 21:29
@alt-glitch alt-glitch added type/security Security vulnerability or hardening comp/cli CLI entry point, hermes_cli/, setup wizard comp/tui Terminal UI (ui-tui/ + tui_gateway/) labels Apr 21, 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 comp/tui Terminal UI (ui-tui/ + tui_gateway/) type/security Security vulnerability or hardening

Projects

None yet

3 participants