hermes doctor 大 WAL 提示:别再让自己成为第二个写者

你的 Hermes 已经连着几周没停过:网关常驻,Desktop 挂在后台,cron 也在按点往库里写东西。周末你想做点负责任的事,敲了一次 hermes doctor,它回你一条警告——WAL 文件太大,建议运行 hermes doctor --fix 来做一次 checkpoint。你照做了,工具也说 checkpoint 完成、修好了一项,一切都显得很合理。直到你回去打开会话,才发现列表缺了一截,或者下一次启动直接报 state.db 损坏。这条建议没有什么恶意,它当时只是不知道数据库正被人用着——2026 年 9 月 16 日合入的 PR #113055,改的就是这一行提示。
症状:诊断输出给了一条会伤人的建议
hermes doctor 是排查配置和依赖问题的诊断命令,hermes doctor --fix 是它的自动修复变体,CLI 文档对这个选项的说明只有一句话:Attempt automatic repairs where possible。
在 #113055 之前,只要 state.db-wal 大于 50 MB,警告就无条件地建议你去跑 --fix,而持有者扫描(holder scan,即判断有没有别的进程正拿着这个数据库的那一步)只在 --fix 内部才执行。矛盾就在这里:--fix 要做的 checkpoint 需要一个安静的数据库,而网关或 Desktop 正开着的时候,它一点都不安静。issue #110054 里的 Discord 讨论串就是这么坏起来的——用户照着这行提示跑了 --fix,于是自己成了第二个写者,而这恰好就是 WAL 被删除时的保护逻辑(deleted-WAL guard)会开火的情形,也是大家报告 state.db 损坏的来源。一条不带条件的建议,代价是别人的会话。
WAL 是什么,多大的 WAL 才算“大”
WAL(write-ahead log,预写日志)是 SQLite 的一种日志模式:新的写入先追加到 state.db-wal 这个文件里,之后再择机合并回主库 state.db。checkpoint 就是“合并回去”这个动作,它会顺便把 WAL 文件缩回去。持有者扫描则是一小段检查:除了自己之外,还有没有别的进程正开着这个数据库。
按这套机制,WAL 文件变大本身就是正常现象:网关、Desktop、cron 一直在往里写,合并不够频繁时文件就会长起来。doctor 也只看两个阈值——超过 10 MB 打一条提示,原文写着 normal for active sessions;超过 50 MB 才升级成警告。也就是说,一个 51 MB 的 WAL 在一台忙着的机器上完全可以是健康的。真正的问题从来不是数字,而是这份 WAL 现在能不能被安全地合并回去。
这次改了什么:先查持有者,再决定让不让你动手
hermes_cli/doctor_state.py 里的 _state_db_wal 现在把持有者检查(hermes_state_repair._live_writer_holds_db,和 _write_health_reason 用的是同一个封装)也放到了警告路径上,于是输出分成几种情况。有持有者、或者根本没法证明数据库是安静的时:
WAL file is large (51 MB) (normal while Desktop or the gateway is running, or state.db cannot be inspected — checkpoint only with them stopped)
Large WAL file — cannot prove state.db is quiet (stop the profile's gateway first, then run 'hermes doctor --fix' to checkpoint)
第一行是这条检查的 warn 文本,第二行是它向 issues 列表里加的那句。没有持有者、WAL 确实可能漏了合并时,措辞换成另一种,而且“先停网关”被排在了 --fix 前面:
WAL file is large (51 MB) (may indicate missed checkpoints)
Large WAL file — stop the profile's gateway, then run 'hermes doctor --fix' to checkpoint
有一点措辞上的讲究值得留意:持有者检查返回“真”的含义是“有人在,或者证明不了没人”,而不是“确定有个活写者”。所以消息里才会写 Desktop or the gateway is running or state.db cannot be inspected —— 查不出来的时候按有人在处理,不把推测说成事实。10 MB 到 50 MB 之间还是那条提示:WAL file is 12 MB (normal for active sessions)。--fix 本身的语义没有改动。
正确的 checkpoint 流程
顺序比命令重要:先停写者,再动手,最后把它们放回去。
hermes gateway status # 先看这个 profile 的网关在不在跑
hermes gateway stop # 在跑就先停掉
hermes doctor # 不带 --fix,先看它现在怎么说
hermes doctor --fix # 只有它不再报"无法证明安静"时才执行
- 停掉写者。 停掉这个 profile 的网关(
hermes gateway stop),把 Desktop 完整退出——是退出,不是最小化;同一个 profile 上开着的终端会话也不要留着。 - 确认它真的安静了。 再跑一次不带
--fix的hermes doctor。如果它仍然说cannot prove state.db is quiet,说明还有东西持有数据库,先去找出来,别硬来。 - 执行修复。
hermes doctor --fix。 - 看结果。 成功时你会拿到一对数字,形如
WAL checkpoint performed (51200K → 176K):箭头前是合并前的 WAL 大小,箭头后是合并后的,单位都是 K。两个数差得越多,说明这次 checkpoint 越值。 - 把服务放回去。 重新启动网关,需要的话再打开 Desktop。
如果第三步输出的是 WAL checkpoint skipped: could not take exclusive ownership of state.db,后面还会跟一句 guard 给出的原因和“停掉网关后重跑”的提示——这就是它遵守承诺的样子:拿不到独占权,就宁可什么都不做。
怎么判断还有没有活着的写者
你不需要自己跑这段扫描,doctor 会跑。它做的事可以一句话概括:先找有没有别的进程持有 state.db,扫不到时再做一次 BEGIN IMMEDIATE 探针来证明这个库确实安静;中间出现任何“说不清”的情况,一律按“有人在”处理。这个探针只在那次扫描确认没有外来持有者之后才跑,所以检查本身不会反过来变成新的第二个写者。
手动能做的部分其实很简单:用 hermes gateway status 看网关状态,在活动监视器或任务管理器里确认 Desktop 是否还在跑,以及别在同一个 profile 上留着跑着的终端会话。三条都清干净了,再考虑 checkpoint。
--fix 现在还能做什么,不能做什么
还能做的: 它依然会执行 PRAGMA wal_checkpoint(PASSIVE),只不过现在是在独占修复守卫(_exclusive_repair_db_guard)之下执行——意思是这一步中间如果有新进程想打开数据库,会被拒绝,而不是被拉进同一次 checkpoint。
做不到时会说: 拿不到独占权就输出 WAL checkpoint skipped: could not take exclusive ownership of state.db,附上 guard 的报错原因和“停掉这个 profile 的网关、重跑 hermes doctor --fix“的提示,同时保留那条”无法证明安静“的 issue,而不是假装修好了。
不会做的: 它不会在线跟一个活着的写者抢 WAL,也不会把一个已经损坏的 state.db 修回来。已经坏过一次的库,走的是另一条路——先看 state.db 自愈那一篇;如果症状是“数据库被锁”而不是“数据库损坏”,处理方式见 state.db 锁问题修复。
顺带说:hermes doctor 的其他输出别自己吓自己
体检输出里绝大部分行都是正常通过的状态,值得单独操心的很少:
- 10 MB 以上的 WAL 提示是信息,不是警报。 原文直接写了 normal for active sessions。
- 大 WAL 加活写者的那条警告,说的也是“这是正常的”。 它唯一的要求是:要 checkpoint,得等网关和 Desktop 都停下来。
- 想把环境一次性讲清楚给人看,用
hermes dump。 它是为粘贴分享设计的纯文本摘要,没有颜色和格式,适合丢进议题或聊天里。
真正需要你动手的只有一种情况:没有持有者,而且 WAL 一直不缩——那时按上面的顺序做一次离线 checkpoint 就够了。命令的完整选项在 hermes doctor 命令参考里。
小结
诊断命令的输出是一条建议,而建议是有前提的。旧版 hermes doctor 在 50 MB 这条线上一律说“去跑 --fix”,从不问一句“这个库现在安静吗”;PR #113055 之后它会先做持有者扫描,有写者(或者证明不了没有写者)就告诉你大 WAL 是正常的、只在该停的时候停下来。这也属于这一轮稳定性修复的一部分,v0.21.2 那批补丁处理的是同一类“工具说了不准确的话”的问题。如果你正打算给长期开着的实例做一次体检,记住顺序就够了:停网关和 Desktop → hermes doctor --fix → 再启动。