Skip to content

/reload-mcp deadlock: _prompt_text_input input() blocks daemon thread in raw mode #23853

@herrschmidt

Description

@herrschmidt

Bug Description

/reload-mcp (and potentially /clear, /new, /reset, /undo which use the same _prompt_text_input pattern) causes a terminal deadlock when dispatched from the process_loop daemon thread.

The symptom: user types /reload-mcp, sees the confirmation dialog ([1] Approve Once / [2] Always Approve / [3] Cancel) rendered above the prompt, but cannot interact with it. The entire SSH session freezes and requires closing the terminal.

Root Cause

cli.py:_confirm_and_reload_mcp_prompt_text_input (line 6018) → when off the main thread, falls back to input(). But input() expects \n as line terminator — however, prompt_toolkit keeps the terminal in raw mode where Enter sends \r. input() blocks forever waiting for \n that never arrives.

The same deadlock occurs in the TUI slash-worker subprocess (tui_gateway/slash_worker.py) where stdin is a pipe, not a TTY — input() reads from the pipe while the parent gateway is blocked waiting for stdout.

Steps to Reproduce

  1. Run hermes (CLI mode, not --tui) in an SSH session
  2. Type /reload-mcp and press Enter
  3. See the [1] Approve Once / [2] Always Approve / [3] Cancel dialog
  4. Cannot type any response — terminal is frozen
  5. Must close terminal and reconnect

Also reproducible via the TUI fallback path:

  1. hermes --tui
  2. If the TUI handler is not found (or for any reason falls through to slash.exec), the slash worker subprocess will deadlock on input()

Expected Behavior

The confirmation should work (or at minimum, not deadlock the terminal).

Proposed Fix

In cli.py:_confirm_and_reload_mcp:

  1. Parse explicit now / always args from command text (matching the TUI handler in ops.ts):

    • /reload-mcp now → skip confirmation, reload once
    • /reload-mcp always → skip confirmation, persist approvals.mcp_reload_confirm: false
  2. Detect non-interactive context before calling _prompt_text_input:

    • If sys.stdin.isatty() is False (pipe/non-TTY) → print warning + suggest now/always syntax, return without blocking
    • If threading.current_thread() is not threading.main_thread() (daemon thread) → same
  3. The existing interactive [1]/[2]/[3] dialog is preserved for the main-thread + TTY case.

Affected Code

  • cli.py lines ~8725–8830: _confirm_and_reload_mcp
  • cli.py lines ~6018–6060: _prompt_text_input (has the input() fallback that blocks in raw mode)
  • Related: /clear, /new, /reset, /undo confirmation in _confirm_destructive_slash_command (lines ~8675+) may have the same issue via their own _prompt_text_input calls

Environment

  • Hermes Agent (CLI mode)
  • SSH session
  • Linux, Python 3.11+
  • prompt_toolkit terminal in raw mode

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High — major feature broken, no workaroundcomp/cliCLI entry point, hermes_cli/, setup wizardcomp/tuiTerminal UI (ui-tui/ + tui_gateway/)tool/mcpMCP client and OAuthtype/bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions