DeepAI Paper Hermes Agent 教程 /reload-mcp 一按就卡死:Hermes CLI 为什么会在确认框里等不到回车?

/reload-mcp 一按就卡死:Hermes CLI 为什么会在确认框里等不到回车?

在命令行工具里,确认框通常是最普通的交互:用户输入一个危险命令,程序弹出 [1] Approve Once / [2] Always Approve / [3] Cancel,然后等待选择。

但 NousResearch/hermes-agent issue #23853 记录了一个典型的终端输入所有权问题:Hermes CLI 中执行 /reload-mcp 后,确认框显示出来了,用户却无法输入,整个 SSH 会话像被冻住一样,只能关闭终端重连。

这个问题表面上像“确认框坏了”,根因其实是 prompt_toolkit 的 raw mode、后台 daemon thread 和 Python 内置 input() 混在一起使用,导致回车字符没有按 input() 期待的方式到达。


现象:确认框出现,但任何选项都输不进去

复现路径很短:

1. 在 SSH 会话里运行 hermes CLI
2. 输入 /reload-mcp
3. 看到确认框:
   [1] Approve Once / [2] Always Approve / [3] Cancel
4. 输入 1、2 或 3 都没有响应
5. 终端会话卡住,只能关闭或 kill

issue 中还提到,类似模式可能影响其它 destructive slash commands,例如:

/clear
/new
/reset
/undo

后续评论确认:这些命令如果走同样的 _prompt_text_inputinput() 路径,也会遇到同类 deadlock。


关键链路:从 /reload-mcp 到后台线程里的 input()

issue 给出的根因路径是:

cli.py:_confirm_and_reload_mcp
        ↓
_prompt_text_input
        ↓
发现当前不在 main thread
        ↓
fallback 到 Python 内置 input()
        ↓
prompt_toolkit 仍然持有 stdin / raw mode
        ↓
input() 等不到它想要的换行
        ↓
终端卡死

_prompt_text_input 的设计意图大概是:

如果在主线程,就使用 prompt_toolkit 兼容的方式提问;
如果不在主线程,就退回普通 input()。

但在 interactive CLI 里,这个 fallback 并不安全。


为什么 raw mode 下 input() 会卡住?

普通终端 canonical mode 下,用户按 Enter,程序通常能读到一行以 \n 结束的输入。

prompt_toolkit 为了实现高级交互,会把终端切到 raw mode。raw mode 下,按键会更直接地传给程序,Enter 可能表现为 \r,而不是 input() 期待的 \n

于是出现错位:

用户按了 Enter
        ↓
raw mode 里产生的是 \r
        ↓
input() 在另一个线程里等 \n
        ↓
永远等不到完整的一行

同时,prompt_toolkit 的主事件循环仍然在管理 stdin。后台线程再调用 input(),等于两个读者争抢同一个输入流。

这类 bug 的本质不是某个选项值解析错了,而是:

终端输入只能有一个明确的 owner。

TUI fallback 里也可能出现同类死锁

issue 还提到 TUI 路径中的 slash worker subprocess:

tui_gateway/slash_worker.py

如果 TUI handler 没有命中,命令可能落到 slash.exec fallback。此时 worker subprocess 的 stdin 可能是 pipe,不是用户直接交互的 TTY。

如果这时调用 input()

worker 从 pipe 读 stdin
parent gateway 等 worker stdout
worker 等用户输入
用户根本输不到 worker stdin

结果就是父子进程互相等待。

这和 CLI raw mode 的表现不同,但原则一样:不要在非交互上下文里突然调用阻塞式 input()


修复思路一:显式参数绕开交互确认

issue 中提出的一个稳妥方向是让 /reload-mcp 支持显式参数:

/reload-mcp now

表示只执行一次 reload,跳过交互确认。

/reload-mcp always

表示持久化关闭 reload 确认,例如:

approvals:
  mcp_reload_confirm: false

这种设计对 CLI/TUI 都友好,因为它把交互选择变成了命令参数。对于自动化、SSH、subprocess、非 TTY 场景,显式参数比弹确认框可靠得多。


修复思路二:非交互上下文直接拒绝阻塞输入

另一个关键保护是,在调用 _prompt_text_input 前先判断当前环境是否真的能交互:

if not sys.stdin.isatty():
    # pipe / non-TTY,不要 input()
    return

if threading.current_thread() is not threading.main_thread():
    # daemon thread,不要 input()
    return

更完整的行为可以是:

打印清晰提示:当前上下文不能交互确认;
建议用户改用 /reload-mcp now 或 /reload-mcp always;
不要让程序进入无限等待。

这类 guard 不只是避免 bug,也能让错误恢复路径更友好。


最终修复:使用 prompt_toolkit-native modal

维护者评论中说明,问题已在 PR #23907 修复。关键方向是:_reload_mcp 确认不再用裸 input()prompt_toolkit 争抢 stdin,而是改用和其他 destructive slash commands 一样的 prompt_toolkit-native modal。

也就是说,修复后的原则是:

既然 CLI 已由 prompt_toolkit 管理输入,确认框也应通过 prompt_toolkit 的机制显示和读取。

这样输入事件、焦点、raw mode 和按键处理都由同一个系统管理,不会出现主线程和后台线程分别读 stdin 的冲突。


为什么这类问题在 SSH 里特别明显?

SSH 会话通常让用户更容易感知“整个终端冻住”:

  • 关闭窗口会断开连接;
  • 按 Ctrl+C 可能不一定被正确处理;
  • 后台线程仍在等待输入;
  • prompt_toolkit 仍然持有 terminal state;
  • 用户看不到更明确的错误提示。

本地终端里可能还能尝试切换、kill、重开 tab;SSH 环境里则更像是会话被锁死。

所以 issue 标注的环境很关键:

Hermes Agent CLI mode
SSH session
Linux / Python 3.11+
prompt_toolkit raw mode

如何排查类似 CLI 确认框卡死?

如果你在任何 Python CLI 工具中遇到“确认框显示了但输不了”的问题,可以按这个顺序看:

1. 确认是否使用 prompt_toolkit / curses / textual 等 UI 框架

这类框架通常会接管 stdin、terminal mode 和键盘事件。

2. 搜索是否有后台线程调用 input()

尤其是类似:

threading.current_thread() is not threading.main_thread()

之后 fallback 到 input() 的代码。

3. 检查 stdin 是否是 TTY

sys.stdin.isatty()

如果是 pipe 或 subprocess stdin,交互式确认通常不应启动。

4. 检查 Enter 的行结束符

raw mode 下可能是 \r,而阻塞式 line input 期待 \n

5. 给 slash command 增加非交互参数

例如:

now
always
yes
--force
--no-confirm

不要把自动化路径绑死在弹窗确认上。


对 MCP reload 命令的启发

/reload-mcp 这类命令比较特殊:它涉及工具列表刷新、MCP server reload、OAuth 状态、可用工具更新等,确实适合保留确认。

但确认不等于一定要弹交互框。

更稳的设计通常是:

交互 CLI:使用 prompt_toolkit-native modal
TUI:使用 TUI 自己的 modal/command handler
非 TTY:拒绝阻塞,提示显式参数
脚本化:支持 now / always / --yes

这样既保留安全性,也不会让终端卡死。


与模型 API 层的边界

这个问题发生在 Hermes CLI / TUI 的输入层:

slash command → confirmation prompt → stdin handling → MCP reload

如果你的 Hermes 同时接了 DeepAI API 中转站或其它 OpenAI-compatible endpoint,模型调用层主要关注:

Base URL
API Key
model id
streaming
tool calling
usage 字段

/reload-mcp 卡在确认框,排查重点应放在 CLI/TUI 的终端输入、线程模型和 MCP reload handler 上。把模型 API 配置和本地输入死锁分层看,能更快定位问题。


FAQ

为什么我看到了确认框,却无法输入 1/2/3?

因为确认框可能是在后台 daemon thread 里调用了 Python input(),而主线程的 prompt_toolkit 仍在管理 stdin。两个输入系统冲突后,input() 等不到完整换行。

/clear/new/reset/undo 也会受影响吗?

如果它们走同样的 _prompt_text_input fallback,就可能受影响。issue 评论明确提到这些 destructive slash commands 也有同类风险。

为什么 SSH 会话里更像“死机”?

因为终端状态被 raw mode 和阻塞输入卡住后,远程会话缺少直观恢复方式,用户往往只能关闭连接或 kill 进程。

修复后应该用什么方式弹确认框?

在 prompt_toolkit 驱动的 CLI 里,应使用 prompt_toolkit-native modal,而不是后台线程里的裸 input()

自动化场景应该怎么执行 /reload-mcp

更可靠的设计是支持显式参数,例如 /reload-mcp now/reload-mcp always,避免在非 TTY 或 pipe 中等待交互输入。


总结

#23853 的核心可以概括为:

Hermes 在 prompt_toolkit raw mode 的 CLI 中,从 daemon thread fallback 到 input(),导致 stdin 所有权冲突和换行符错位,最终让 /reload-mcp 确认框卡死。

这类问题的修复原则很清楚:

不要在非主线程或非 TTY 环境里调用阻塞式 input();
交互确认应交给当前 UI 框架;
自动化路径应提供显式参数绕过弹窗。

对 CLI/TUI 工具来说,确认框不是小细节。它位于安全操作和用户控制之间,一旦输入层设计不稳,就会把一个普通确认变成整个会话的死锁点。

Related Post

Hermes Agent 常见问题排查:从 GitHub Issues 看 403、502、Docker 后端和自定义接口坑Hermes Agent 常见问题排查:从 GitHub Issues 看 403、502、Docker 后端和自定义接口坑

翻看 Hermes Agent GitHub Issues 后整理的排错复盘:Custom Endpoint 403、后台 review agent、Windows localhost 502、terminal.backend 残留 Docker 配置、Feishu 表格渲染等问题。

Gemini 报 Multiple authentication credentials?Hermes Agent 接入 Google OpenAI-compatible 端点的排错指南Gemini 报 Multiple authentication credentials?Hermes Agent 接入 Google OpenAI-compatible 端点的排错指南

Hermes Agent 接入 Gemini provider 报 HTTP 400 Multiple authentication credentials received?这通常不是简单 API Key 粘错,而是 Google OpenAI-compatible endpoint、Bearer、x-goog-api-key、AIza legacy key 与 AQ 新 key 的认证兼容问题。本文总结排查顺序、临时绕法和 native Gemini adapter 的长期方向。

Hermes 接 Langfuse 为什么没日志?已解决的 placeholder key silent failure 排查Hermes 接 Langfuse 为什么没日志?已解决的 placeholder key silent failure 排查

Hermes Agent 已解决 Langfuse placeholder key 静默失败、trace 缺少 input/output、post_tool_call 不触发等观测问题。本文面向 DeepAI API 中转站用户,讲清可观测性、日志完整性和 hook 排查顺序。