26 KiB
| name | version | description |
|---|---|---|
| sqlite-db-corruption-recovery | 1.0.0 | Use when SQLite/state.db 报损坏/malformed/corruption。11 种损坏模式分类排查 + .recover/VACUUM INTO 重建 + 误判循环规避。 |
SQLite DB 损坏彻底排查与修复(2026-09-02 新发现)
背景
牧尘反馈「今天 DB 数据损坏好几次,让其他 agent 修复几次」。我彻底排查后发现四种不同类型的损坏,根因是内存压力 + 人为 truncate。
损坏模式分类
模式 1:0 字节 truncate(人为删除)
| DB | mtime | 特征 |
|---|---|---|
/home/muc/.hermes/cron.db |
2026-09-02 08:06 | 主 cron DB 被清空 |
/home/muc/.hermes/profiles/prof-b/cron/cron.db |
2026-07-25 20:35 | prof-b cron 被清空 |
/home/muc/.hermes/hermes-agent/state.db |
2026-07-25 20:37 | 进程状态被清空 |
判定方法:
# 找所有 0 字节 .db 文件
find /home/muc/.hermes /var/lib -name "*.db" -size 0 2>/dev/null
# 对比 mtime
stat -c "%y %n" /home/muc/.hermes/cron.db
根因:不是 crash,是有人用 truncate 或 > file.db 命令手动清空。
处置:
- 检查是否有 cron 任务在写这些 DB(可能写失败时误删)
- 检查
/var/log/audit/audit.log(如有)看谁执行了 truncate - 重建:停相关服务 → 从 git archive 恢复 → 重启
⚠️ 重要修正(2026-09-03 重启后验证):以下 3 个"0 字节 DB"不是真存储:
| 文件 | 真实存储位置 |
|---|---|
/home/muc/.hermes/cron.db |
不是——cron/jobs.json 才是 hermes cron 真存储(91 个 jobs 完好,142KB) |
/home/muc/.hermes/profiles/prof-b/cron/cron.db |
不是——prof-b 也用 jobs.json |
/home/muc/.hermes/hermes-agent/state.db |
孤儿文件——hermes-agent 不再写这个 |
判定方法(用前先验证):
# 1. 找 hermes 实际使用的存储
find /home/muc/.hermes -name "jobs.json" -o -name "*.json" 2>/dev/null | head -5
ls -la /home/muc/.hermes/cron/jobs.json
sqlite3 /home/muc/.hermes/cron.db ".tables" # 如果 0 字节,这条会失败
# 2. 真存储是 jobs.json(hermes v0.21+)
wc -c /home/muc/.hermes/cron/jobs.json
python3 -c "import json; d=json.load(open('/home/muc/.hermes/cron/jobs.json')); print(f'jobs: {len(d.get(\"jobs\", d))}')"
# 3. cron 命令实际工作吗?
hermes cron list | head -10
结论:删除 0 字节孤儿 DB 不会影响 cron / hermes 任何功能。已验证(2026-09-03 重启后 cron 91 jobs 全在,jobs.json 142KB 完好)。
模式 2:NULL 约束违反
-- state.db 的 delivery_obligations 表
sqlite3 /home/muc/.hermes/state.db "PRAGMA table_info('delivery_obligations')"
-- 列:obligation_id, session_key, platform(NOT NULL), chat_id(NOT NULL), ...
-- 实际有记录 platform IS NULL → integrity check 失败
根因:代码 INSERT 时遗漏了必填字段(platform/chat_id),或 schema 已变但写入逻辑未同步。
处置:
-- 先确认哪些列是 NOT NULL
PRAGMA table_info('delivery_obligations');
-- 清空损坏记录(这些记录业务上已无效)
DELETE FROM delivery_obligations WHERE platform IS NULL OR chat_id IS NULL;
-- 验证
PRAGMA integrity_check;
模式 3:索引条目数不一致
Error: wrong # of entries in index idx_messages_session_id
Error: wrong # of entries in index idx_messages_session_active
Error: wrong # of entries in index idx_messages_session
根因:写入过程中进程被杀(OOM swap full → Linux 杀进程)→ SQLite 索引未完整更新。 处置:
-- 重建索引(prof-b state.db)
DROP INDEX IF EXISTS idx_messages_session_id;
DROP INDEX IF EXISTS idx_messages_session_active;
DROP INDEX IF EXISTS idx_messages_session;
CREATE INDEX idx_messages_session_id ON messages(session_id);
CREATE INDEX idx_messages_session_active ON messages(session_id, is_active);
PRAGMA integrity_check;
模式 4:database disk image is malformed
Error: stepping, database disk image is malformed
根因:WAL 文件存在但 main DB 被 truncate 清空 → 无法一致性恢复。
处置:sqlite3 .recover 或从 backup 恢复。
模式 5:hermes cron create 静默写 0 字节(2026-09-02 新发现)
$ hermes cron create "every 24h" --name "_trigger" --no-agent --script "true"
Created job: f68f6434e1e4 ← 报告成功
$ ls -la /home/muc/.hermes/cron.db
-rw-r--r-- 1 muc muc 0 9月 2 21:57 ← 仍是 0 字节
根因:hermes cron create 内部走 gateway 内存对象,不直接写 cron.db;gateway 调度器在后台批量刷盘时如果检测到 db 文件存在但 schema 错位,可能直接清空重写。这是 hermes v0.21 的隐性 bug。
判定:
hermes cron create "every 1h" --name "_test" --no-agent --script "true" 2>&1
# 立刻 ls -la cron.db,如果仍是 0 字节 = bug
正确重建流程(绕过 create 路径):
# 1. 隔离损坏文件(不删!保留供后续查证)
mv /home/muc/.hermes/cron.db /home/muc/.hermes/cron.db.broken.$(date +%Y%m%d)
# 2. 找 .archive 里的 jobs 备份(8/30 备份的 cron-jobs JSON 救过命)
ls /home/muc/.hermes/.archive/*/cron-jobs*.json 2>/dev/null
# 3. 用 hermes cron create 重新建(隔离后 create 会自动建 schema)
# 注意:必须先删 0 字节文件,create 才会建新 schema
hermes cron create "every 1h" --name "_placeholder" --no-agent --script "true"
# 4. 验证 cron.db 不再是 0 字节
ls -la /home/muc/.hermes/cron.db
sqlite3 /home/muc/.hermes/cron.db ".tables" # 应该有表
# 5. 从 JSON 备份批量恢复
python3 /home/muc/.hermes/scripts/restore-cron-jobs.py \
/home/muc/.hermes/.archive/omniroute-shutdown-20260902-0218/cron-jobs-8d61456cc1c3-updated.json
模式 7:Gateway 重启触发的 FTS + busy_timeout 假阳性损坏(2026-09-03 彻查 → 2026-09-03 17:55 根治完成)
症状:gateway 突然报 "No reply: the turn was stopped because the state database reported structural corruption"。95% 概率 DB 实际健康,是 busy_timeout=0 误诊。
判断方法(必跑):
sqlite3 ~/.hermes/state.db "PRAGMA integrity_check;" # → ok(DB 实际健康)
sqlite3 ~/.hermes/state.db "PRAGMA busy_timeout;" # → 100(已修,原 0)
sqlite3 ~/.hermes/state.db "SELECT COUNT(*) FROM messages, messages_fts;" # → 双份 72k
根因:
- FTS5 双份索引写入放大:
messages72k 行 +messages_fts72k 行(自动同步副本)→ state.db 325MB,每条消息写两遍 busy_timeout=0(hermes_state.py:1612)→ 写冲突时立即抛错,不等待- 每次 gateway 重启 → 8 个读 fd + 1 个写 fd 同时抢 → busy_timeout=0 抛 "database is locked" → 上层解读为 "structural corruption"
- 恢复路径是"动手术"不是"防御":每次"自动恢复"生成 .corrupt-*.db 备份(VACUUM 需要 2× 空间)
完整 7 步拉现状 + 5 个根治方案 + 代码位置:见 references/state-db-corruption-restart-loop-20260903.md
根治实施记录(2026-09-03 17:55 commit 678c4506e5 + stable tag):
| 层 | 改动 | 文件 | 状态 |
|---|---|---|---|
| L1 systemd | ExecStartPre=stabilize.py + MemoryHigh=1500M + MemoryMax=2G | ~/.config/systemd/user/hermes-gateway.service |
✅ daemon-reload(不重启 gateway,PID 111258 维持) |
| L2 源码 P0 | hermes_state.py:1612 busy_timeout=0 → 100ms |
hermes_state.py |
✅ 已 commit |
| L3 源码 P1 反向论证 | hermes_state.py:1218 加注释:journal_size_limit 是 connection-level |
hermes_state.py |
✅ 已 commit |
| L4 文档同步 | ~/mc/小唯/07-Wiki/concepts/state-db-corruption-fix-plan.md v1.0 → v1.1 |
概念文档 | ✅ 已 commit 8b4a863 |
| L5 watchdog | cron 774986811686 每 30min no-agent 检查 |
~/.hermes/scripts/state-db-watchdog.py |
✅ 已在跑 |
🔴 任务包教训(下次必然再踩,固化成铁律):
-
任务包 P1 写"加
conn.commit()让journal_size_limit进 db header 持久化"是错的- SQLite 官方文档明确:"The setting does not persist. Changing this setting in one connection does not affect any other connections."
- 任务包作者(我)没查文档就拍方案——错的
- hermes-agent 自己的注释(line 1218)写"每次新连接必须重新设置"才是对的
- 反向论证后:已加 3 行注释澄清,下次维护者不会再误以为"该持久化没做"
-
方案 P0 写"busy_timeout 0 → 30000(30s)"太大
- 这是切换 journal_mode 期间的临时窗口(毫秒级),不需要 30s 容忍
- 30s 会让连接长时间 hang,反而掩盖问题
- 最终值 100ms:足够避开切换窗口的写竞争,又不会长时间阻塞
修复方向(按优先级,已实施标注):
| 优先级 | 方案 | 改动量 | 状态 |
|---|---|---|---|
| 🔴 P0 | busy_timeout 0 → 100ms(hermes_state.py:1612) |
1 行 | ✅ 已 commit |
| 🟡 P1 | FTS 降为 content=external 或异步合并 |
schema 改动 | ⏸️ 暂缓(边际收益低) |
| 🟡 P1 | hermes sessions optimize 启动后延迟 5 分钟 |
config | ⏸️ 暂缓 |
| 🟢 P2 | 抑制 gateway 重启循环(9-2 21:52-22:06 重启 5 次) | supervisor | ⏸️ 暂缓 |
| 🟢 P2 | 每日 state.db vacuum 看门狗 | cron | ⏸️ 暂缓 |
判定流程(2026-09-03 牧尘原话:"state.db 为什么反复损坏?"):
- 先
PRAGMA integrity_check—— 大概率 ok - 再
PRAGMA busy_timeout—— 应为 100(已修,原为 0) - 拉 gateway 重启时间线 —— 大概率每次重启后都有 .corrupt-*.db
- 不直接
hermes doctor --fix/.recover(恢复路径本身有副作用) - 走 PR 给 hermes-agent 上游合并 commit
678c4506e5(busy_timeout 100ms)
模式 7:Gateway 内部禁止 self-restart(2026-09-02 新发现)
$ systemctl --user restart hermes-gateway
Blocked: command cannot restart, stop, or uninstall the gateway from inside the gateway process.
The gateway would kill this command before it could complete (SIGTERM propagates).
Run `hermes gateway restart` from a separate shell outside the running gateway.
根因:Hermes 运行时检测到你在自己的 gateway 进程内执行重启命令,自动拦截避免 SIGTERM 自杀。
正确做法:
- 在 IDE/外部终端执行
hermes gateway restart - 或用 dbus-send 绕过 systemd 限制(之前 prof-b 修过)
- 或等系统 watchdog 触发自动重启
判定:如果一个"重启服务"命令被 BLOCKED 而不是直接执行 = 你在 gateway 进程内。
模式 8:hermes cron script 字段不接受参数(2026-09-03 血泪教训)
# ❌ 错 1:把参数当文件名找
"script": "state-db-watchdog.py check"
# → Script not found: /home/muc/.hermes/scripts/state-db-watchdog.py check
# ❌ 错 2:用 bash -c 包(系统拼到路径下找)
"script": "bash -c \"python3 /home/muc/.hermes/scripts/state-db-watchdog.py check\""
# → Script not found: /home/muc/.hermes/scripts/bash -c "..."
# ✅ 对:写 wrapper .sh 脚本
"script": "state-db-watchdog-cron.sh"
# wrapper 内容:exec /home/muc/.hermes/hermes-agent/.venv/bin/python /home/muc/.hermes/scripts/state-db-watchdog.py check
根因:hermes cron create --script 字段设计就是单条可执行文件路径(不带参数)。.sh/.bash 自动走 bash,其他走 Python。
参考其他 cron 的正确做法:
stock_daily_signal_paper.sh(包stock_signal.py --code 000858 --paper)stock_daily_health.sh(包stock_daily_health.py --watchdog)- 任何需要传参的 cron,都先写 wrapper
.sh
判定:如果你想给 cron 的脚本加 --watch / --check / 任何 flag,先写 wrapper。
模式 9:告警持续刷屏(2026-09-03 用户反馈 → 降噪设计)
症状:state.db 损坏 → watchdog 每 30 min 飞书发一次"失败"消息 → 主人飞书堆满红点。
根因:watchdog 用 if report["issues"]: send_alert(),只要有问题就发。
修复(已部署在 state-db-watchdog.py):状态文件 + 状态变化检测。
# 状态文件:/tmp/state-db-watchdog-state.json
# 字段:{"has_issues": bool, "last_alert_at": iso, "last_issues": [...]}
prev_state = json.loads(state_file.read_text()) if state_file.exists() else {}
state_changed = (cur_has_issues != prev_state.get("has_issues", False))
if report["issues"]:
if not state_changed:
# 持续异常 → 静默,不发飞书
log("🔇 已知问题(不重复告警)")
return 1
# 状态好→坏 → 发告警
send_alert()
else:
if state_changed and prev_state.get("has_issues"):
# 状态坏→好 → 发恢复通知
send_recovery()
关键设计:
- 持续异常静默(不刷屏)
- 状态变化才发(坏→好 / 好→坏各发一次)
- 修复后自动发"已恢复"(让主人知道下次重启生效了)
重要陷阱:部署降噪时,先把状态文件预设为"已知坏"(含旧 last_alert_at),避免部署瞬间触发"新故障"告警(我犯过,发了 2 条无用飞书消息)。
# 部署后立即:
cat > /tmp/state-db-watchdog-state.json <<EOF
{
"has_issues": true,
"last_alert_at": "2026-09-03T11:50:19", # 旧告警时间
"last_issues": ["open_failed_database disk image is malformed"]
}
EOF
模式 10:Gateway 重启 = Agent 失忆(2026-09-03 主人原话"你会不断重复工作")
症状:每次 gateway 重启后,会话上下文清空,agent 完全忘了"之前在干什么"。
对策(必做):
- 进度标记文件:写到
/tmp/state-db-fix-progress.md(或任务相关路径)- 包含:当前阶段、已完成、待自然发生、不要做、应急命令
- 失忆恢复第一步:先读这个文件
- 快照备份:每次大改前
cp关键文件到/home/muc/.hermes/backups/<task>-<timestamp>/ - 回滚脚本:写到
~/.hermes/scripts/restore-<task>.sh,从独立终端跑(不能在 gateway 内部) - 修挂到下次自然重启:把"必须重启才能修"的操作挂到 systemd
ExecStartPre(如 stabilize.py),不要主动停 gateway - 避免自我重启(见模式 6)
判定流程:如果一个任务需要"停 gateway → 改 → 启 gateway",先停下来问主人——99% 有不重启的做法。
模式 11:深层页引用瑕疵 = "永久性假 malformed" + 误判恢复循环(2026-09-04 根治)
症状:messages 等全表可读、gateway 完全正常写入,但 PRAGMA integrity_check / quick_check 报 malformed。持续数天,每次重启/检测都触发一次"损坏→恢复→再损坏"循环。
根因:修复时遗留的深层页引用瑕疵(例:Tree 60 page 51046 cell 206: 2nd reference to page 63262)。messages 数据完好(72440 条可读、FTS 同步),但某索引/页引用错 → 所有完整性检查报 malformed → 检测工具(watchdog / stabilize 自动恢复)误判"损坏" → cp 快照覆盖 + unlink WAL → 把好库搞坏/丢数据 → 恶性循环。
判定流程(2026-09-04 铁律):
# 1. 数据是否真在?→ 逐表 count(跳过 integrity_check)
~/.hermes/hermes-agent/.venv/bin/python -c "
import sqlite3
c = sqlite3.connect('file:/home/muc/.hermes/state.db?mode=ro&immutable=1', uri=True, timeout=10)
for t in ['messages','sessions','system_prompts','delivery_obligations']:
try: print(t, c.execute(f'SELECT count(*) FROM {t}').fetchone()[0])
except Exception as e: print(t, 'ERR', str(e)[:60])"
# → 全部可读 = 数据没坏,只是深层瑕疵
# 2. 用 gateway 同款 venv python(3.53.1)验证,不要用系统 CLI 3.45.1(旧版误报更多)
根治 = VACUUM INTO / .recover 重建(2026-09-04 执行,72447 条全保 + integrity=ok):
# 1. 停 gateway(必须!不能在运行中操作)
systemctl --user stop hermes-gateway
# 2. 重建(VACUUM INTO 优先:保 schema+FTS;若坏页太深失败则用 sqlite3 .recover)
~/.hermes/hermes-agent/.venv/bin/python -c "
import sqlite3
src = sqlite3.connect('/home/muc/.hermes/state.db', timeout=60)
src.execute(\"VACUUM INTO '/tmp/state.db.clean'\")"
# 3. 验证新库 quick_check=ok + messages 条数
# 4. mv 坏库 → 备份;mv clean → state.db;systemctl --user start hermes-gateway
# 5. 重启后 immutable 只读跑完整 integrity_check 确认 ok
🔴 血的教训(2026-09-04,三条铁律):
-
gateway 运行中禁止任何外部进程普通打开 state.db(读写模式)!
- 02:03 用 venv python 普通 connect(非 immutable)打开运行中库 → 触发 recovery 与 gateway 并发 → 制造损坏
- 02:14 有人 mv state.db → gateway 继续写 deleted(黑洞)WAL → 约 20 分钟 session 消息丢失
- 检测只能用
file:...?mode=ro&immutable=1;替换必须先停 gateway
-
有"自动恢复"能力的脚本(stabilize.py 等)可能自己就是损坏源:
- 检测误判(深层瑕疵让 integrity 报 malformed)→ 触发自动恢复(cp 快照覆盖 + unlink WAL)→ 每次重启覆盖活动库 → 越修越糟
- 自动恢复必须有"gateway 运行保护"(pgrep 检测到 gateway 在跑就跳过),且检测必须 immutable 只读 + 正确 SQLite 版本
-
"sqlite3 CLI 能读 / python 报 malformed" ≠ 库坏了:可能是 SQLite 版本差异(CLI 3.45.1 vs venv 3.53.1)+ WAL 缺失假象。先逐表 count + immutable 复检,别急着恢复
判定:integrity_check 报 malformed 但全表可读 + gateway 正常 → 深层瑕疵 → VACUUM INTO/.recover 重建,不是恢复快照(快照丢数据)。
模式 12:单表 B-tree 局部损坏(2026-09-05 实测根治 — 最快最无损)
症状:integrity_check 报 malformed,但 messages/sessions 等大表逐表 count 完全可读、gateway 持续正常写入(文件头正常、无 -wal/-journal)。只有某一张表 count 报 database disk image is malformed。本次为 delivery_obligations(第 3 次出问题:09-04 NULL platform / 09-05 凌晨全库 / 09-05 下午局部)。
定位三步:
# 1. 逐表 count(跳过 integrity)→ 找坏表
python -c "import sqlite3; c=sqlite3.connect('file:/home/muc/.hermes/state.db?mode=ro&immutable=1',uri=True); [print(t, c.execute(f'SELECT count(*) FROM {t}').fetchone()[0]) for t in ['messages','sessions','system_prompts','delivery_obligations']]"
# 2. 副本普通连接复检(排除 immutable 误报;cp 到 /tmp 再连,不碰 live 库)
cp ~/.hermes/state.db /tmp/check.db && python -c "import sqlite3; c=sqlite3.connect('/tmp/check.db'); print(c.execute('PRAGMA integrity_check').fetchone()[0])"
# 3. 拿坏表 schema(sqlite_master 通常完好)
python -c "import sqlite3; c=sqlite3.connect('file:...mode=ro&immutable=1',uri=True); [print(s) for _,n,s in c.execute(\"SELECT type,name,sql FROM sqlite_master WHERE tbl_name='<坏表>'\").fetchall() if s]"
修复 = 停 gateway → DROP + 按原 schema 重建空表 → 启动(2026-09-05 14:30 实测,messages 74244 一条不丢):
# systemd-run 独立 unit 执行(gateway 停止不影响脚本完成)
systemd-run --user --unit=state-db-fix --collect bash /tmp/fix-delivery-obligations.sh
# 脚本逻辑:cp 备份 → systemctl stop gateway → python DROP+CREATE → integrity 验证 → start gateway
判定:坏表在健康快照里本就是 0 行(delivery_obligations 空表常态)→ 重建空表无损;若快照里该表有业务数据,重建后从快照 ATTACH 导入。优点:比全库 VACUUM INTO / 快照覆盖快几个量级(几秒 vs 分钟),messages 全保。
🔴 三坑(2026-09-05 血泪):
- stabilize auto_restore 是重启定时炸弹:integrity malformed 时,下次 gateway 重启 ExecStartPre stabilize --apply 检测到损坏 → 自动 cp 最新快照覆盖全库(丢自快照以来全部消息)。→ 必须在重启前修,否则任何重启都丢数据。
- 停 gateway 死锁:gateway SIGTERM 后等待子进程退出;persistent execute_code kernel 是 gateway 子进程,持续活跃会阻塞退出(实测 deactivating 卡 3 分钟)。破局:
systemd-run --user systemctl --user kill -s KILL hermes-gateway(独立 unit,杀 gateway 不影响自身);terminal 内直接 kill 会被 Blocked 拦截。 - 查 state.db 前先看内存:本机 llama-server(闲置无连接)曾吃掉 7.6GB → swap 打满 → thrashing → cp 356MB 超时 420s → 此期间 SQLite 写坏单表。检查顺序:
free -h→ps -eo pid,pcpu,pmem,rss,cmd --sort=-pmem | head→ 停闲置大进程 → 再查 DB。gateway cgroup 2G 上限内跑 >2G 任务也会被静默 SIGKILL(见 cbm-index-cgroup-oom 记录)。
修复实录:/tmp/fix-delivery-20260905.log(14:26 备份 → 14:30 DROP+重建 → integrity ok / messages 74244 全保 → gateway 新 PID 启动)。备份:state.db.pre-fix-20260905_142649。
内存压力(根因)
Mem: 15Gi total, 1.8Gi available
Swap: 1.9Gi/1.9Gi (FULL)
GPU: 3709MiB/4096MiB (91%)
1.9G swap 全满 → OOM killer 随机杀进程 → 进程写 DB 时被杀 → 索引错乱/文件损坏。
长期方案:
- 扩 swap 到 4G:
sudo fallocate -l 4G /swapfile4 && sudo mkswap /swapfile4 && sudo swapon /swapfile4 - 或减少内存占用:停不用服务(ComfyUI 等)
- 加内存监控 cron(
free -h> 2G available 时飞书告警)
排查命令速查
# 1. 扫所有 DB integrity
for db in /home/muc/.hermes/*.db /home/muc/.hermes/**/*.db /var/lib/new-api/*.db; do
[ -f "$db" ] || continue
size=$(stat -c%s "$db")
[ "$size" -eq 0 ] && echo "❌ 0B: $db"; continue
result=$(sqlite3 "$db" "PRAGMA integrity_check;" 2>&1)
echo "$result" | grep -q "^ok$" || echo "❌ $db: $result"
done
# 2. 找 0 字节文件
find /home/muc ~/.hermes /var/lib -name "*.db" -size 0 2>/dev/null
# 3. 看内存压力
free -h; cat /proc/meminfo | grep -E "MemAvailable|SwapFree"
# 4. 看 OOM killer
dmesg | grep -i "oom\|killed process" | tail -5
SQLite PRAGMA 持久化分类(避免下次再踩)
journal_size_limit / busy_timeout / cache_size / mmap_size / temp_store 是 connection-only,commit 不进 db header,跨连接不保留。application_id / user_version / synchronous / auto_vacuum 是 schema-level,持久化到 db header。foreign_keys 看起来像 schema 但其实是 connection-only。
实战结论:任何"修 PRAGMA 让它跨 gateway 重启保留"的尝试,先查 SQLite 文档确认是不是 connection-only;conn.commit() 对 connection-only PRAGMA 无效。详见 references/state-db-corruption-restart-loop-20260903.md §八。
🔴 任务包/方案写作的反模式(2026-09-03 教训,固化为铁律)
错误示范(实际发生在 P1 任务包里):
"在
_apply_wal_size_limit后加conn.commit()让 PRAGMA 进 db header 持久化"
为什么是错的:
- SQLite 官方文档明确:journal_size_limit 是 connection-level,不能持久化
- 任务包作者(我)没查文档就拍方案——错误信息写进任务包
- opencode / 后续 agent 拿到这个任务包会直接执行错方案
避免方法(4 步):
- 任何"修某 PRAGMA 让它持久化"的方案,先查 SQLite 官方文档(https://sqlite.org/pragma.html)的"Does this pragma persist?"段
- 写任务包/方案前,先打开 SQLite 跑一次
PRAGMA xxx; PRAGMA xxx=value; conn.commit(); conn.close()重连,看新连接读到什么 - 如果 PRAGMA 是 connection-only,方案应该是"在
_init_schema调用_apply_*"(在每次开连接时设),而不是"加 commit()" - 如果不确定,方案里写 "⚠️ 待验证:connection-level 还是 schema-level?查文档 + 实测"
判定:写"让 PRAGMA 持久化"类任务时,先打开 SQLite 测试连接再写文字,别凭直觉。
cron.db 重建实录(2026-09-02 真实事件)
8/30 备份的 47 个 jobs JSON 救了命。完整恢复流程:
# 1. 隔离损坏文件(保留供查证,不删)
mv /home/muc/.hermes/cron.db /home/muc/.hermes/cron.db.broken.$(date +%Y%m%d)
# 2. 找 jobs 备份
ls /home/muc/.hermes/.archive/*/cron-jobs*.json 2>/dev/null
# 3. 隔离 0 字节后,hermes cron create 会自动建 schema
hermes cron create "every 24h" --name "_placeholder" --no-agent --script "true"
ls -la /home/muc/.hermes/cron.db # 验证不再是 0 字节
sqlite3 /home/muc/.hermes/cron.db ".tables" # 看 schema
# 4. 删 placeholder
PLACEHOLDER_ID=$(hermes cron list 2>&1 | grep -B 1 "_placeholder" | grep "ID:" | awk '{print $2}' | head -1)
hermes cron remove "$PLACEHOLDER_ID"
# 5. 批量恢复 47 个 jobs(解析 JSON 调 hermes cron create)
# 见脚本 ~/.hermes/scripts/restore-cron-jobs-from-archive.py
完整脚本(已写到 ~/.hermes/scripts/restore-cron-jobs-from-archive.py):解析 cron-jobs-*.json,按 schedule/no_agent/script 字段还原。
防御措施
1. db-monitor.sh(已部署)
/home/muc/.hermes/scripts/db-monitor.sh 每 30 分钟检查:
- cron.db 大小(不能 0 字节)
- 其他 5 个关键 DB 的 integrity_check
- 内存压力(free < 200M 告警)
2. 定期 JSON 备份 cron
建议每周一次:
hermes cron list --json > /home/muc/.hermes/.archive/cron-jobs-$(date +%Y%m%d).json
3. 写测试(升级后必跑)
hermes cron create "every 1h" --name "_write_test" --no-agent --script "true"
SIZE=$(stat -c %s /home/muc/.hermes/cron.db)
[ "$SIZE" = "0" ] && echo "❌ BUG: cron.db 写 0 字节" && 飞书告警
hermes cron remove <id>
参考
- 本报告完整记录:
~/.hermes/docs/db-investigation-20260902.md - 9-3 重启后验证 + 4 套记忆系统全景 + tencentdb service 修复:
references/db-corruption-reboot-verification-20260903.md - LanceDB 损坏恢复:
lancedb-corruption-recoveryskill - 看门狗:
health-watchdog.sh已在监控内存,但阈值需要调(当前 80%/90%,应加 swap 满告警) - 2026-09-03 state.db 反复损坏彻查:
references/state-db-corruption-restart-loop-20260903.md(FTS5 + busy_timeout=0 假阳性,5 个根治方案)