"会话找不到了"?state.db 现在会自检、隔离和自愈

你打开 Hermes 想找回昨天的会话,却只看到一句冷冰冰的 session not found——但你清清楚楚记得它存在。更糟的情况:state.db(Hermes 存放所有会话历史的 SQLite 数据库)整个损坏,几个月的对话记录一起“消失”。在过去,这类问题几乎没法诊断:错误信息把真正的病因(数据库损坏)藏了起来,而系统还会继续往损坏的数据库里写数据,把损失越扩越大。8 月 30-31 日合入的一批修复(PR #99513 及其姊妹 PR)让 Hermes 第一次系统地学会应对数据库损坏:识别它、隔离它、能修就修、修不了就明确告诉你。
以前为什么这么难查
state.db 是 SQLite 文件,理论上很皮实,但在断电、磁盘写满、进程崩溃、多进程并发打开等场景下依然可能损坏。过去 Hermes 对损坏的态度是“假装没事”:
- 读到坏数据时,界面只显示
session not found,把“数据库坏了”这个真相吞掉; - 结构上已经损坏的库继续接受写入——新数据写进一个坏掉的库,等于往漏水的桶里倒水;
- 一个 0 字节的空文件会被误当成“新数据库”正常打开,等真正要写的时候才报
attempt to write a readonly database; - 多个进程同时打开同一个库时,一个进程甚至可能把另一个进程刚创建的合法空库误判为损坏而隔离掉。
这次修了什么
这批修复横跨 6 个 PR,覆盖检测、隔离、自愈、防护四个环节:
识别损坏(而不是假装没事)
- 报“损坏”而不是报“session not found”(PR #99529):损坏检测前移到读取路径,问题一出现就明确提示,而不是让用户对着一个误导性错误发呆;
- 结构损坏的库停止接受写入(PR #99652):fail-closed,宁可拒绝写入也不让数据进坏库;
- 读连接按文件而不是按对象限流(PR #98691):之前按 SessionDB 实例限制连接数,多实例时连接数失控,是文件句柄耗尽和崩溃的诱因之一。
隔离 0 字节文件
- 0 字节截断的 state.db 会被隔离(quarantine)(PR #98017):启动时把空文件移走,而不是当成正常库打开;
- 跨进程锁修复并发隔离竞态(PR #99513 子集群 A):检查 → 隔离 → 连接 → schema 提交的整个过程被一个跨进程锁包住,并用
has_live_connection()保护“正在被合法使用的空库”,杜绝一个进程误隔离另一个进程刚创建的库。
自愈 FTS 全文索引
- UnicodeDecodeError 不再击穿索引探针(PR #99513 子集群 B):全文索引(FTS)表损坏时可能抛出解码错误,之前会直接炸掉只读初始化和所有读取接口;现在探针和修复逻辑都捕获这类异常,损坏的 FTS 表会被删除重建(drop-and-recreate)。
消除崩溃类竞态
- 共享写连接的未同步读取被消除(PR #99502):这类问题在极端情况下会导致 SIGSEGV 级别的崩溃;
- close() 与后台写入的竞态自愈(PR #99509):写连接关闭时若还有在途写入,系统自我修复而不是静默丢数据。
对你的实际意义
如果你的会话历史曾经莫名其妙“消失”过,这批修复改变的是整个处理流程:检测靠前 → 隔离坏文件 → 修得动就修(FTS 表)→ 修不动就明确报错,且绝不继续往坏库里写。会话数据的可信度,从“可能哪天就没了”变成“出了问题至少说得清楚、坏文件不再扩大伤害”。
何时能用上
这些 PR 于 2026 年 8 月 30-31 日合入,目前都在 main 分支,尚未进入正式发布版本。更新到最新 main 即可获得全部防护;如果之前遇到过 session not found 或会话丢失,升级后值得留意错误提示的变化——现在它会直接告诉你数据库状态。
会话历史是你和 Hermes 之间的“工作记忆”,它的可靠性值得认真对待。想了解会话数据的其他保护手段,可以看 会话导出与保存指南 和 798 条消息失忆事件复盘;压缩机制对历史的影响见 一键压缩指南。