DeepAI Paper Hermes Agent 教程 Hermes 命令一跑就卡死:install.sh 重跑后为什么把 CLI 入口改成了自我调用

Hermes 命令一跑就卡死:install.sh 重跑后为什么把 CLI 入口改成了自我调用

有些安装问题不是“没装上”,而是更烦:看起来装好了,但命令一执行就没反应。

NousResearch/hermes-agent issue #21454 记录的就是这种情况:用户之前已经安装过 Hermes,后来重新运行新版 install.sh,结果 hermeshermes doctorhermes gateway 全部挂住,没有输出,直到手动 Ctrl+C。

这不是模型慢,也不是 DeepAI、OpenAI-compatible API、网络代理或 provider 配置问题。

根因在安装脚本:新版 install.sh 想把 ~/.local/bin/hermes 写成一个清理 Python 环境变量的 bash wrapper,但旧安装里这个路径本来是指向 venv 内 pip console script 的 symlink。脚本用 cat > ~/.local/bin/hermes 写文件时,shell 会沿着 symlink 写到目标文件,于是把真正的 venv/bin/hermes 覆盖成了一个调用自己的 wrapper。

结果就是:

hermes -> venv/bin/hermes -> exec venv/bin/hermes -> exec venv/bin/hermes -> ...

一个漂亮的无限递归。


典型症状:Hermes CLI 没报错,只是卡住

受影响时,表现通常不是 traceback。

而是:

hermes

没输出。

hermes doctor

没输出。

hermes gateway

也没输出。

命令像是在运行,但不会进入正常逻辑。

如果你刚刚重跑过:

curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash

并且之前机器上已经有旧版 Hermes 安装,那么这个 issue 的匹配度就很高。


旧安装留下了什么?

issue 里说,旧版安装脚本的 setup_path() 大致是这样做的:

ln -sf "$HERMES_BIN" "$command_link_dir/hermes"

也就是说,它会创建:

~/.local/bin/hermes -> ~/.hermes/hermes-agent/venv/bin/hermes

这里的 venv/bin/hermes 是 pip 生成的 Python console script,内容大概是:

#!/home/user/.hermes/hermes-agent/venv/bin/python3
import sys
from hermes_cli.main import main
if __name__ == "__main__":
    sys.exit(main())

这个结构本身没问题。

~/.local/bin/hermes 是门牌,真正入口在 venv 里。


新脚本想做什么?

后来某次改动为了避免继承外部 Python 环境变量,把入口改成了 bash wrapper。

逻辑类似:

cat > "$command_link_dir/hermes" <<EOF
#!/usr/bin/env bash
unset PYTHONPATH
unset PYTHONHOME
exec "$HERMES_BIN" "\$@"
EOF

这个设计意图是合理的:

  • 清掉 PYTHONPATH
  • 清掉 PYTHONHOME
  • 再执行 venv 内真正的 Hermes

问题出在写入目标。

如果:

$command_link_dir/hermes

已经是一个 symlink,那么:

cat > "$command_link_dir/hermes"

不会替换 symlink 本身,而是沿着 symlink 写到目标文件。

于是它实际覆盖的是:

~/.hermes/hermes-agent/venv/bin/hermes

也就是 pip 生成的真实入口。


最坏的结果:venv/bin/hermes 变成“调用自己”的脚本

覆盖后,venv/bin/hermes 可能变成:

#!/usr/bin/env bash
unset PYTHONPATH
unset PYTHONHOME
exec "/home/user/.hermes/hermes-agent/venv/bin/hermes" "$@"

注意最后一行:

exec "/home/user/.hermes/hermes-agent/venv/bin/hermes" "$@"

它执行的正是自己。

所以每次运行 hermes doctor,都会变成:

venv/bin/hermes exec venv/bin/hermes
venv/bin/hermes exec venv/bin/hermes
venv/bin/hermes exec venv/bin/hermes
...

没有进入 Python,也没有进入 hermes_cli.main

这就是为什么它不是报错,而是“挂住”。


官方修复方向:写 wrapper 前先 rm -f 旧路径

issue 里给出的修复非常直接:

mkdir -p "$command_link_dir"
rm -f "$command_link_dir/hermes"
cat > "$command_link_dir/hermes" <<EOF
#!/usr/bin/env bash
unset PYTHONPATH
unset PYTHONHOME
exec "$HERMES_BIN" "\$@"
EOF
chmod +x "$command_link_dir/hermes"

关键是这一行:

rm -f "$command_link_dir/hermes"

它先删除旧的 symlink。

这样后面的:

cat > "$command_link_dir/hermes"

写出来的就是一个新的普通文件,而不会穿透 symlink 覆盖 venv/bin/hermes

评论里也确认:该问题与 #21332 同源,PR / commit 链路中提到 #25734、commit c75e1a03f 已修复 scripts/install.sh::setup_path(),通过 rm -f 防止 redirect 跟随陈旧 symlink。


怎么判断自己是不是这个问题?

1. 看 ~/.local/bin/hermes 是不是 symlink

ls -l ~/.local/bin/hermes

如果看到类似:

/home/user/.local/bin/hermes -> /home/user/.hermes/hermes-agent/venv/bin/hermes

说明你有旧安装遗留的 symlink 结构。

2. 看 venv/bin/hermes 内容

head -20 ~/.hermes/hermes-agent/venv/bin/hermes

正常情况下,它应该是 Python console script,包含类似:

from hermes_cli.main import main

如果你看到:

#!/usr/bin/env bash
unset PYTHONPATH
unset PYTHONHOME
exec "/home/user/.hermes/hermes-agent/venv/bin/hermes" "$@"

那基本就是入口被覆盖了。

3. 看命令是不是全部无输出卡住

hermes doctor

如果没有任何输出且一直挂起,也符合这个问题。


已经中招怎么临时恢复?

issue 里给了恢复思路:把 venv/bin/hermes 恢复成 pip console script。

如果旁边有 venv/bin/hermes-agent,可以用它作为模板生成 hermes

评论中有人用过类似方法:

cd ~/.hermes/hermes-agent
mv venv/bin/hermes venv/bin/hermes.broken.bak
sed 's|from run_agent import main|from hermes_cli.main import main|' \
    venv/bin/hermes-agent > venv/bin/hermes
chmod +x venv/bin/hermes
hash -r
hermes doctor

注意:这属于针对当时环境的 workaround。不同版本的 console script 内容可能不同,最稳妥的方式还是使用包含修复的新版安装脚本重新安装,或者重新生成 venv 内 console entry point。


为什么这是安装脚本 bug,不是 shell PATH 问题?

PATH 问题通常表现为:

command not found

或者执行到了旧版本。

#21454 里,hermes 确实能被找到,也确实执行了。

它卡住,是因为入口脚本内部自我 exec

所以重点不是:

echo $PATH

而是检查:

ls -l ~/.local/bin/hermes
head -20 ~/.hermes/hermes-agent/venv/bin/hermes

对 DeepAI API 中转站用户的边界说明

如果你用 Hermes 接 DeepAI API 中转站,通常会关注:

  • OPENAI_BASE_URL
  • https://api.deepai.wang/v1
  • API Key
  • 模型名
  • OpenAI-compatible provider 配置

但这个问题发生得更早。

Hermes CLI 入口还没进入 Python 主程序,当然也不会进入 provider 初始化、HTTP 请求或模型调用阶段。

所以:

  • DeepAI 不能修复 venv/bin/hermes 被覆盖;
  • OpenAI-compatible API 配置不会影响这个无限递归;
  • 换模型、换 key、换 base URL 都没用;
  • 先修 CLI entry point,再谈模型 API。

FAQ

为什么重跑 install.sh 会把旧安装弄坏?

因为旧安装留下的 ~/.local/bin/hermes 是 symlink。新版脚本直接 cat > ~/.local/bin/hermes 时,写入穿透 symlink,覆盖了 venv 内真实入口。

为什么命令没有报错?

入口脚本不断 exec 自己,没有进入 Python 主程序,所以不会打印正常错误信息。

rm -f 为什么能修复?

它先删除旧 symlink,再写入 wrapper。这样写入目标是新的普通文件,而不是 symlink 指向的 venv/bin/hermes

这个问题和 #21332 是一回事吗?

评论中维护者指出它与 #21332 是同一根因:commit 043a118d4 后的 install.sh wrapper 写入逻辑跟随旧 symlink,造成 self-exec loop。

DeepAI API 中转站配置会导致这个问题吗?

不会。这是本地安装器/CLI entry point 问题,发生在任何模型 API 调用之前。


总结

#21454 的教训很简单:

覆盖可执行入口前,先确认目标不是旧 symlink。

安装脚本要从“创建 symlink”迁移到“写 wrapper 文件”时,必须先:

rm -f "$command_link_dir/hermes"

否则 cat > 可能不是在写 wrapper,而是在把 venv 里的真实 Python entry point 改成一个会永远调用自己的脚本。

Related Post

长任务最怕的不是中断,而是压缩时悄悄失忆长任务最怕的不是中断,而是压缩时悄悄失忆

Hermes 长任务遇到 incomplete chunked read 时,context compression 可能插入 static fallback marker 并移除中间 turns,却没有生成真实摘要。本文复盘 #18458:为什么压缩摘要失败比普通中断更危险,以及 retry/fallback 应该如何设计。

一个工具会失败,两个工具反而可能成功:Hermes Memory 调度路径的反直觉 bug一个工具会失败,两个工具反而可能成功:Hermes Memory 调度路径的反直觉 bug

Hermes Honcho memory tools 在单工具调用时返回 Unknown tool,但多工具并发路径有时正常。本文复盘 #15118:schema 已注入不代表执行层会路由成功,sequential dispatch 漏掉 MemoryManager.has_tool() 才是真正根因。

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

Hermes CLI 执行 /reload-mcp 后确认框显示出来,却无法输入 1/2/3,SSH 会话像被冻住。本文客观复盘 #23853:prompt_toolkit raw mode、daemon thread 中的 input() fallback、 / 行结束符错位、TUI slash worker pipe 死锁,以及 prompt_toolkit-native modal 的修复方向。