Hermes Agent 实战 11|踩坑合集:当「手动 rm」从来不是真正的修复原创
# 踩坑合集:当「手动 rm」从来不是真正的修复
系列第 11 篇。这篇收一堆不够单独成篇、但每个都真实坑过我的小事。它们有个共同主题:很多「问题」是配置问题,不是定期打扫问题——你
rm一次很爽,过几天原样长回来。识别「这是配置病还是卫生病」,是运维一个 agent 部署的关键直觉。
# 1. Checkpoint 把磁盘吃满——而 rm 治标不治本
每个 Profile 有 checkpoints/ 目录,会膨胀到几个 GB。我清过一次:profiles/ 从 35G 压到 5.6G。然后过些天,它一模一样地长回了原来的大小(某几个 Profile 精确地回到上次清理前的体积)。
这就是「配置病」的典型:
根因是
config.yaml的checkpoints:块里auto_prune: false。一旦关着,max_total_size_mb上限根本不被强制执行,快照无限增长。手动rm只是把症状抹掉,病还在。
真正的修复是改配置,不是定期打扫:
# profiles/<name>/config.yaml
checkpoints:
auto_prune: true # ← 真正的修复在这里
max_snapshots: 20
max_total_size_mb: 500
2
3
4
5
# 清理时的安全边界:
# - legacy-* 老归档:随便删,安全
# - store/ :v2 实时影子存储,别动,留给 GC
# 改完所有 Profile 配置后重启所有网关
systemctl --user restart 'hermes-gateway-*'
2
3
4
5
⚠️ 有个同名陷阱:
checkpoints:块和cron:块各有一个auto_prune。你要改的是checkpoints:那个,别动cron:的。
可复现的判断法:任何「清理后又长回来」的东西,先别急着写定时清理脚本——先找有没有一个被关掉的自动回收开关。长回原样,就是配置病的指纹。
# 2. 跨 Profile SSH 泄漏:状态没干净地随切换重置
这个在第 02、10 篇都提过,这里给现象:在 WebUI 里切换 Profile 后,新会话却复用了上一个 Profile 的 SSH 后端——命令静默地跑到了错误的远程机器上。
根因是 _resolve_container_task_id() 在多 Profile 的 WebUI 进程里把不同会话都解析成了同一个 "default" 槽位,导致环境复用。修复是按会话隔离(session:<key>)。
注意边界:per-Profile 的网关进程不受影响(各自独占一个
"default"),只有多 Profile 共享的 WebUI 进程有这个风险。知道「谁会中招、谁不会」,排查时能少走一半弯路。
# 3. Profile 目录里莫名多出的空 workspace/
某个 Profile 目录下突然冒出一个空的 workspace/,看着像出 bug 了。其实是良性的:当 Profile 通过 WebUI(而非专用网关进程)跑会话时,会建这个目录;它只在 SSH 后端真正被用到时才有内容。
教训:不是所有「异常文件」都是问题。在删一个你没亲手建的东西之前,先搞清它是谁、什么时候建的——这条对 agent 自己也成立(见下一条)。
# 4. 「澄清瘫痪」:把模糊当借口反过来盘问
这是 agent 行为层面的坑,但同样真实。有一次我只说三个字:
记录这个
上下文里明明已经有一大段清晰的内容要记。但 agent 没动手,反而去加载了一堆笔记类技能,然后弹出「你要存成 Memory 还是文件还是 Obsidian?」的选择题,制造决策疲劳,最后什么都没记下来。
反模式:澄清瘫痪。 当用户带着充分上下文说「记录这个」,agent 应该推断最可能的默认值(比如一个 markdown 文件)直接执行,而不是把模糊当成停下来盘问的借口。主动行动 > 被动提问。
# 5. 幻觉成功:网络层失败了却假装搞定
另一类:底层(网络/拉取)失败了,agent 却给出含糊的「成功」收尾。日志里明明是一连串 pull 失败,会话却以一种「大概好了」的语气结束。
对策:对「必须验证终态」的操作,强制 agent 在收尾前复核最终状态,而不是假设已解决。「我执行了 X」和「X 真的生效了」是两回事——这条贯穿整个系列(DBA 巡检要复核、cron 要发心跳、策略更新要验证回滚)。
# 6. 一条贯穿所有坑的元规则
回头看,这些坑几乎都能归到同一句话:
分清「配置病」和「卫生病」。
- checkpoint 膨胀 = 配置病(关掉的 auto_prune),不是卫生病(定期 rm)。
- SSH 泄漏 = 状态管理病,不是「重启一下就好」。
- 幻觉成功 = 验证缺失,不是「再问一遍」。
每次你准备写一个「定时清理 / 定期重启 / 反复确认」的脚本时,先停一秒问自己:我是在治病,还是在反复擦同一摊水? 多数时候,真正的开关藏在配置里。
# 7. 可复现 checklist
- 清理后又长回来 → 先找被关掉的自动回收开关(checkpoint 的
auto_prune是头号嫌疑)。 - 多 Profile 共享进程是状态泄漏的高发区;知道哪些进程独占、哪些共享。
- 删之前先认领:不是你建的东西,先查清来历再动手。
- 带足上下文的指令直接执行,别用澄清当拖延。
- 强制复核终态,杜绝幻觉成功。
最后一篇,把镜头拉到 Hermes 之外:我用 Claude Code 怎么运维 Hermes 本身、opencode / ACP 怎么接、一个监听新版本就自动更新的 webhook——以及一个有趣的闭环:这个系列本身,就是用 AI 把上万次会话提炼出来的。
# 6. 完整诊断命令清单
把所有踩坑时真正用过的诊断命令整理成一份表,方便 ctrl+F:
| 问题 | 诊断命令 | 修复动作 |
|---|---|---|
| 磁盘被 checkpoint 吃满 | du -sh ~/.hermes/profiles/*/checkpoints/ | 改 checkpoints.auto_prune: true + systemctl --user restart hermes-gateway-<name>.service |
| 跨 Profile SSH 泄漏 | grep -n "HERMES_SESSION_KEY" ~/.hermes/hermes-agent/tools/terminal_tool.py | 补丁丢失时从 tag commit 重打 |
| Gateway 起不来 | systemctl --user status hermes-gateway-* | journalctl --user -u hermes-gateway-<name>.service -n 50 看错误 |
| 端口被占用 | ss -tlnp \| grep <port> | kill <pid> 或改 unit 的 ExecStart |
| Telegram token 撞车 | curl -s "https://api.telegram.org/bot<TOKEN>/getUpdates?offset=-1&timeout=0" | 释放 polling 锁,重启真轮询者 |
| state.db 损坏 | sqlite3 ~/.hermes/state.db 'PRAGMA integrity_check;' | 不通过 → 备份后重建 |
| 某个 Profile 没状态 | sqlite3 ~/.hermes/profiles/<name>/state.db 'SELECT COUNT(*) FROM sessions;' | 为 0 = 没建会话过;为 1+ = 正常 |
| agent 装睡(静默失败) | hermes logs --level WARNING --limit 50 | 找 [tool] 行的 ERROR;通常是 dangerous-command 拦截 |
| state.db 全文搜不到 | sqlite3 ~/.hermes/state.db "SELECT name FROM sqlite_master WHERE type='table' AND name='messages_fts';" | 没 FTS5 = state.db 是旧版本,需重新初始化 |
# 6.1 快速修复清单(按出现频率排)
# 1. Gateway 重启(90% 的问题)
systemctl --user restart 'hermes-gateway-*'
# 2. checkpoint 清理(确认是配置病而不是卫生病)
# 先:du -sh ~/.hermes/profiles/*/checkpoints/
# 再:改 config.yaml 的 checkpoints.auto_prune: true
# 不:rm -rf ~/.hermes/checkpoints/ ← 治标不治本
# 3. state.db 体检
for p in ~/.hermes/profiles/*/; do
echo "== $(basename $p) =="
sqlite3 "$p/state.db" "PRAGMA integrity_check;" 2>/dev/null \
|| echo " no state.db"
done
# 4. 找出当前活跃 Profile
cat ~/.hermes/active_profile
# 5. 看哪个 Profile 在长连 Telegram
ss -tnp | grep 443 | grep python
# 6. 看哪个 Profile 把磁盘写爆
du -sh ~/.hermes/profiles/*/ | sort -h | tail -5
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 7. Agent 可直接解析的元数据块
{
"_meta": {
"doc_version": "2025-01-15",
"article_id": "hermes-11-pitfalls",
"profile_context": "any",
"executable_examples": true,
"dependencies": ["hermes-cli", "sqlite3", "systemd"],
"estimated_setup_time": "N/A (reference only)"
},
"quick_fixes": {
"checkpoint_full": "rm -f ~/.hermes/checkpoints/sessions/*.json",
"checkpoint_size": "du -sh ~/.hermes/checkpoints/",
"checkpoint_fix": "sed -i 's/auto_prune: false/auto_prune: true/' ~/.hermes/profiles/<name>/config.yaml",
"gateway_list": "systemctl --user list-units 'hermes-gateway*'",
"gateway_restart": "systemctl --user restart hermes-gateway-<name>.service",
"gateway_logs": "journalctl --user -u hermes-gateway-<name>.service -n 50 --no-pager",
"state_db_query": "sqlite3 ~/.hermes/state.db 'SELECT COUNT(*) FROM sessions;'",
"state_db_check": "sqlite3 ~/.hermes/state.db 'PRAGMA integrity_check;'",
"profile_switch": "hermes --p<PASSWORD> <name>",
"logs_check": "hermes logs --level WARNING --limit 20",
"telegram_unlock": "curl -s 'https://api.telegram.org/bot<TOKEN>/getUpdates?offset=-1&timeout=0'",
"disk_usage": "du -sh ~/.hermes/profiles/*/ | sort -h"
},
"diagnostic_commands": {
"disk_space": "df -h ~",
"session_size": "du -sh ~/.hermes/sessions/ ~/.hermes/state.db",
"gateway_status": "systemctl --user status hermes-gateway-*",
"active_profile": "cat ~/.hermes/active_profile",
"profile_list": "ls -la ~/.hermes/profiles/",
"telegram_conn": "ss -tnp | grep 443 | grep python",
"port_in_use": "ss -tlnp | grep <port>"
},
"known_pitfalls": [
"checkpoint 无限增长导致磁盘满 → 改 auto_prune: true 而不是 rm",
"switch profile 时 SSH backend 泄漏 → 检查 tools/terminal_tool.py 的 _resolve_container_task_id",
"gateway 没重启导致 cron job 不生效 → Python 模块缓存",
"state.db 损坏导致历史会话丢失 → PRAGMA integrity_check 体检",
"token 撞车导致 Telegram 接收失败 → getUpdates?offset=-1 释放 polling 锁"
],
"safety_rules": [
"清理 checkpoint 前先 du -sh 确认占用",
"重启 gateway 前先 list-units 确认 unit 名",
"操作 state.db 前先备份 cp state.db state.db.bak",
"修改 profile 配置后必须验证切换",
"删任何文件前先 df -h 看磁盘"
],
"verification": {
"checkpoint_clean": "test $(ls ~/.hermes/checkpoints/sessions/*.json 2>/dev/null | wc -l) -eq 0",
"gateway_active": "systemctl --user is-active hermes-gateway-default.service",
"state_db_ok": "sqlite3 ~/.hermes/state.db 'PRAGMA integrity_check;' | grep -q '^ok$'",
"profile_readable": "test -r ~/.hermes/active_profile",
"disk_free": "df -h ~ | awk 'NR==2 {print $4}' | grep -qG '[0-9]M'"
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
AI Agent 使用指南:
- 当用户说"checkpoint 满了" → 先
quick_fixes.checkpoint_size确认占用,再quick_fixes.checkpoint_fix修配置(不要 rm) - 当用户说"gateway 起不来" → 执行
quick_fixes.gateway_logs看错误,常见是端口占用或配置文件错 - 当用户说"state.db 好像坏了" → 先
quick_fixes.state_db_check体检,不通过则cp state.db state.db.bak备份后重建 - 当用户说"我要切换 profile" → 执行
quick_fixes.profile_switch然后verification.profile_readable验证 - 当用户说"Telegram bot 不收消息" → 先
diagnostic_commands.telegram_conn看是否多进程,再quick_fixes.telegram_unlock释放 polling 锁 - 当用户报任何问题 → 先按
safety_rules确认安全边界,再动手