memory_search 反复 fallback?一次配置冲突引发的血案
qmd 数据库完好无损,为什么 memory_search 还是 fallback 到本地引擎?

memory_search 反复 fallback?一次配置冲突引发的血案
引言
升级 OpenClaw 到 2026.5.12 后,memory_search 突然"哑火"了——明明配了 qmd 向量搜索引擎,结果每次查询都 fallback 到本地小模型,搜索质量断崖式下跌。
更诡异的是:qmd 数据库完全正常,1934 个文档、14966 个向量,integrity_check 全通过。那 unable to open database file 到底是什么意思?
这是一次完整的踩坑记录,包含 5 个阶段的排查、3 个错误方向、和 1 个最终用 lsof 找到的意外根因。
症状
先看看问题长什么样。调用 memory_search 时,返回 JSON 里的关键字段:
{
"results": [],
"provider": "local",
"model": "sentence-transformers/all-MiniLM-L6-v2",
"fallback": {
"from": "qmd",
"reason": "unable to open database file"
},
"debug": {
"backend": "builtin",
"effectiveMode": "n/a",
"searchMs": 2000,
"hits": 0
}
}provider 应该是 "qmd" 却变成了 "local",还带一个让人误判的 "unable to open database file"。
所有 agent 的 memory_search 都受影响,搜索质量大幅下降(本地小模型 vs qmd 向量搜索+reranking),搜索延迟也增加了(本地模型推理慢)。

第一阶段:怀疑数据库损坏
最直觉的反应——数据库文件坏了或权限不对。
# 检查 qmd 数据库完整性
sqlite3 ~/.openclaw/agents/main/qmd/xdg-cache/qmd/index.sqlite "PRAGMA integrity_check;"
# 返回:ok
# 检查文档数量
sqlite3 ~/.openclaw/agents/main/qmd/xdg-cache/qmd/index.sqlite "SELECT count(*) FROM documents;"
# 返回:1934
# 检查向量数量
sqlite3 ~/.openclaw/agents/main/qmd/xdg-cache/qmd/index.sqlite "SELECT count(*) FROM content_vectors;"
# 返回:14966所有 10 个 sqlite 文件全部通过 integrity_check。sqlite3 能正常打开,权限也没问题。
结论:数据库没问题,unable to open database file 不是字面意思。
教训:SQLite 的 “unable to open database file” 错误码有时是逻辑层面的判断(如"数据库不可用"),不一定是文件系统级别的"打不开"。需要看 lsof 确认实际打开的文件。
第二阶段:追踪 qmd 调用链
既然数据库本身没问题,那问题出在调用链路上。
查看 qmd-gateway 脚本发现,它设置了独立的 XDG 路径:
#!/bin/bash
export XDG_CONFIG_HOME="/home/administrator/.openclaw/agents/main/qmd/xdg-config"
export XDG_CACHE_HOME="/home/administrator/.openclaw/agents/main/qmd/xdg-cache"
exec /home/administrator/.bun/bin/qmd "$@"这意味着存在两套数据库并存:
- qmd 原生数据库:
~/.cache/qmd/index.sqlite(32MB,400 文件) - OpenClaw 管理的 qmd 数据库:
~/.openclaw/agents/main/qmd/xdg-cache/qmd/index.sqlite(106MB,1934 文档)
qmd 本身能正常工作:
/usr/local/bin/qmd-gateway search "test" -c memory-dir-main
# 正常返回结果但 OpenClaw 日志里藏着关键错误:
qmd session-start sync failed: Error: qmd update failed (code 1):
[Error: EIO: i/o error, scandir '/mnt/g/Documents/Obsidian-oklife/oklife/AI/05-智能体/AI Agents智能体']原来是 qmd 同步文件时遇到了 EIO 目录,导致 process crash。当时判断这就是 unable to open database file 的直接原因。
第三阶段:EIO 目录的噩梦
问题目录:/mnt/g/Documents/Obsidian-oklife/oklife/AI/05-智能体/AI Agents智能体
这是一个 WSL 2 环境下 NTFS 通过 9p 协议挂载的目录(/mnt/g/ 指向 Windows G 盘)。
排查时间线:
- 在 Windows 侧删除重建该子目录 → 没用,WSL 9p 层仍然报 EIO
- 执行
chkdsk G: /f→ G 盘被卸除,/mnt/g挂载点失效 - 尝试手动
mount -t 9p重新挂载 → 失败,WSL 内核没有编译 9p 模块 - 用户重启 WSL(
wsl --shutdown)→ 挂载恢复,但目录 EIO 依然 - 再次在 Windows 侧剪切到桌面再粘贴回来 → 改名后 EIO 仍然
关键发现:即使目录重建,WSL 内核 9p 层缓存了旧的 inode 状态。stat 显示 inode 号完全一样(844424930139347),scandir 持续报 EIO。
WSL 9p 的限制:
- 9p 文件系统模块没有
modprobe,不能手动加载 - 只能依赖 WSL 自动挂载机制
- 某些 NTFS 特殊目录状态通过 9p 暴露给 Linux 时,会导致元数据操作失败
教训:
chkdsk会卸除卷,导致/mnt/g失效,需要重启 WSL- WSL 9p 目录缓存无法通过常规手段清除
- 不要在 WSL 排查上过度发散,先回到核心问题
第四阶段:ignorePatterns 的尝试
既然修不了 EIO 目录,就想在配置里排除它。这是一系列失败的尝试:
尝试 1:在 openclaw.json 中添加 ignorePatterns
{
"path": "/mnt/g/Documents/Obsidian-oklife/oklife/AI",
"name": "ai",
"pattern": "**/*.md",
"ignorePatterns": ["**/05-agent智能体/**", "**/05-智能体/**"]
}结果:openclaw config validate 报错:Unrecognized key: "ignorePatterns"。qmd 不支持此字段。
教训:不要向配置文件添加未经文档确认的字段,会导致 config validate 报错,污染配置。
尝试 2:在 qmd index.yml 中添加 exclude_patterns
ai:
path: /mnt/g/Documents/Obsidian-oklife/oklife/AI
pattern: "**/*.md"
exclude_patterns:
- "05-agent智能体/**"
- "05-智能体/**"结果:memory_search 仍然 fallback。qmd 也不支持 exclude_patterns 字段。
尝试 3:通过 config.patch / config.apply 修改 memory.qmd.paths
→ 失败:memory.qmd.paths 是受保护路径,不允许通过 patch 修改
最终用户手动清理了 ignorePatterns 配置,config validate 恢复通过。
第五阶段:lsof 一句话破案
排查已经陷入僵局——EIO 修不了、配置改不了、排除字段不支持。
转折点是用 lsof 看 Gateway 进程到底打开了什么文件:
lsof -p $(pgrep -f "openclaw.*gateway") | grep sqlite结果让人大跌眼镜——Gateway 打开的是:
/home/administrator/.openclaw/memory/oc-engineer.sqlite不是 qmd 的 index.sqlite!
检查这个 builtin 数据库:
sqlite3 ~/.openclaw/memory/oc-engineer.sqlite ".tables"
# chunks, chunks_fts, files, embedding_cache, meta
sqlite3 ~/.openclaw/memory/oc-engineer.sqlite "SELECT count(*) FROM chunks;"
# 0
sqlite3 ~/.openclaw/memory/oc-engineer.sqlite "SELECT count(*) FROM files;"
# 0数据库结构在,但数据为空。再对照配置:
{
"memory": {
"backend": "qmd"
},
"agents": {
"defaults": {
"memorySearch": {
"provider": "local",
"model": "all-MiniLM-L6-v2",
"fallback": "none"
}
}
}
}根因找到了:
memory.backend = "qmd"和memorySearch.provider = "local"配置冲突- OpenClaw 执行 memory_search 时,先检查
~/.openclaw/memory/<agent>.sqlite - 该数据库 chunks=0, files=0 → 判定"数据库不可用"
- 报
unable to open database file,然后 fallback 到 local 内置引擎

根因总结
| 层级 | 原因 |
|---|---|
| 直接原因 | builtin 数据库(oc-engineer.sqlite)是空的,OpenClaw 判定其不可用 |
| 根本原因 | memory.backend 与 memorySearch.provider 配置不一致 |
| 加剧因素 | qmd 同步时遇到 EIO 目录 crash,让排查方向跑偏 |
排查时间线
| 阶段 | 耗时 | 方向 | 结果 |
|---|---|---|---|
| 数据库检查 | 8min | sqlite 损坏/权限 | ❌ 数据库正常 |
| qmd 调用链 | 27min | qmd 配置问题 | ⚠️ 发现 EIO 错误 |
| EIO 目录修复 | 4h+ | 9p/chkdsk/重建 | ❌ 无法修复 |
| ignorePatterns | 44min | 配置排除 | ❌ qmd 不支持 |
| lsof 破案 | 5min | 实际文件操作 | ✅ 找到根因 |
讽刺的是:耗时最短的 lsof 一步破案,而 EIO 目录花了 4 小时却发现是无关因素。
完整教训清单
技术层面
- 配置一致性第一:
memory.backend和memorySearch.provider必须对齐,否则会导致不可预测的 fallback - “unable to open database file” 不一定是字面意义:可能是逻辑判断(如"数据不可用"),需要
lsof确认实际打开的文件 - WSL 9p 目录缓存:某些 NTFS 特殊状态会导致 scandir EIO,且 WSL 内核会缓存目录状态
- SQLite 错误码的多义性:同一个错误码可能代表不同的逻辑含义
排查方法层面
- 先看日志,再猜原因:日志里有完整的错误链,不应该瞎猜
- 用 lsof/strace 确认实际行为:不要假设代码按你想象的方式执行
- 从最简单的原因开始排查:配置冲突 > 文件权限 > 文件系统 bug
- 不要过度发散:本次排查在 EIO 目录上浪费了大量时间,根因却是配置问题
OpenClaw 运维层面
- 升级后要检查所有核心功能:从 2026.5.6 升级到 2026.5.12 后,memory_search 一直有问题但没发现
- 配置验证要全面:
openclaw config validate应该成为每次配置变更后的标准步骤 - 备份配置:修改前应该备份,今天多次通过 gateway 工具修改配置,没有手动备份
待解决事项
- 配置对齐:需要将
memory.backend和memorySearch.provider对齐(都改为 qmd 或都改为 builtin) - builtin 数据库填充:如果继续使用 builtin 后端,需要让 OpenClaw 填充
~/.openclaw/memory/<agent>.sqlite - EIO 目录:
/mnt/g/Documents/Obsidian-oklife/oklife/AI/05-agent智能体仍然存在 EIO,但不影响 qmd 工作(已排除在扫描范围外)
关键命令备忘
# 检查 gateway 打开的 sqlite 文件
lsof -p $(pgrep -f "openclaw.*gateway") | grep sqlite
# 检查 builtin memory 数据库内容
sqlite3 ~/.openclaw/memory/oc-engineer.sqlite "SELECT COUNT(*) FROM chunks; SELECT COUNT(*) FROM files;"
# 检查所有 sqlite 数据库完整性
find ~/.openclaw -name "*.sqlite" -exec sqlite3 {} "PRAGMA integrity_check;" \;
# 查看 qmd 相关日志
rg "qmd|memory.*backend|memory.*fallback|unable.*open" /tmp/openclaw/openclaw-$(date +%Y-%m-%d).log
# 验证配置
openclaw config validate关联阅读
- [[2026-07-04-1118-wsl-9p-protocol-bug-memory-search|memory_search 突然变弱?一次 WSL 9p 协议 Bug 引发的血案]]
参考来源
–全文完–

梦行志
