首页
/ claude-mem 搜索与 Chroma 稳定性修复实录:MCP 500 错误、Collection 初始化失败与静默断连的三层防线

claude-mem 搜索与 Chroma 稳定性修复实录:MCP 500 错误、Collection 初始化失败与静默断连的三层防线

2026-09-04 09:23:07作者:魏献源Searcher

本文以 claude-mem 仓库中 Phase 07 分诊手册 TRIAGE-07-Search-Chroma-Stability.md 为主体,完整还原“MCP 搜索返回 500、Chroma Collection 初始化失败、Worker 看似健康但 MCP 实际断连”三类线上问题的诊断路径与修复方案,并结合仓库源码(SearchRoutes.tsSearchManager.tsChromaMcpManager.ts 等)解析当前的错误处理、降级回退与自动重连实现。读完后你将能够独立定位 claude-mem 搜索链路的故障层(路由层 / Chroma MCP 子进程层 / 连接管理层),并按仓库既有的验证流程完成回归。

问题定位:为什么搜索稳定性是“中等优先级”的头号议题

分诊手册开篇给出了本阶段的定位:搜索是 claude-mem 面向用户的核心能力,而这一组 bug 会让 MCP 搜索要么返回 HTTP 500,要么根本无法连接 Chroma,直接侵蚀产品的核心价值主张。修复点集中在两个层面:搜索编排层(search orchestration)和 Chroma 连接层(Chroma connection layer)。

该手册明确列出了本次处理的四个 issue:

Issue 症状 故障层
#1263 MCP search 工具经 Worker API 返回 HTTP 500,用户端只看到不透明的错误 搜索路由 / 搜索编排
#1261、#1232 Chroma 连接失败,报 "Collection setup failed",疑似 chroma-mcp 子进程死亡或未启动 Chroma 子进程 / 数据目录初始化
#1266 Worker 的 localhost:37777 健康检查正常,但 MCP 工具全部失效 连接管理 / 健康探测盲区

手册同时声明了前置依赖:Phase 01–06 应已完成,尤其是 Phase 06 的 Windows Chroma 修复和 Phase 02 的 Chroma CPU 修复——因为部分 500 错误可能正是上游 Chroma 就绪性检查(Phase 06 中 #1225 的 readiness poll)未修复时暴露出来的下游症状。这一依赖关系提醒我们:稳定性问题必须按分诊阶段顺序收敛,后序阶段的 500 修复可能是前序修复的“自然结果”。

问题一:MCP 搜索 Worker API 500 错误(#1263)

手册给出的修复方案

手册要求首先读取搜索路由处理程序(在 src/services/worker/http/routes/ 下找 SearchRoutes)和搜索编排逻辑(SearchManager.ts),判断 500 很可能来自搜索管线中一个未被捕获的异常,然后为搜索路由添加结构化错误处理:

  1. 用 try/catch 包裹搜索处理器;
  2. 遇到 Chroma 错误时,回退到仅 SQLite 的搜索(查找现有的 chromaSync === null 处理路径);
  3. 遇到 SQLite 错误时,返回带语义的错误消息并使用 HTTP 503(而非 500);
  4. 以 ERROR 级别记录真实错误及完整堆栈。

手册还给出了一条判断分支:如果错误特指 Chroma collection 错误,那么 Phase 06(#1225 就绪轮询)的修复可能已解决它;如果 Chroma 客户端在查询中途断开,则应在 ChromaMcpManager.callTool() 中加入重连逻辑。

当前仓库中的落地实现

路由层的统一错误处理。 当前实现中,SearchRoutes.ts 注册的每条路由(GET /api/searchGET /api/search/observationsGET /api/search/by-fileGET /api/timelineGET /api/context/*POST /api/context/semantic)都不是裸写 handler,而是统一通过 this.wrapHandler(...) 包装。BaseRouteHandler.ts 中的 wrapHandler 同步/异步双路捕获异常,交给 handleErrorBaseRouteHandler.ts#L84-L113):AppError 按其声明的 statusCode 响应,非 AppError 归一为 500 并记录 { error: message } 的 JSON 响应体;同时区分日志级别——4xx 客户端错误按 WARN 记录且不进错误上报通道,5xx 真正故障才走 logger.failure(含脱敏的异常上报)。这正是手册第 3、4 条要求(“有意义的错误消息 + 恰当状态码 + ERROR 级日志”)的结构性落地:错误处理不是每个路由各自打补丁,而是收敛到路由基类一处

编排层的三条搜索路径与 FTS5 回退。 手册第 2 条要求的“Chroma 失败时回退 SQLite 搜索”,在 SearchManager.tssearch() 中体现为三条显式路径:

  • PATH 1(仅过滤,无 query 文本):直接走 SQLite 过滤(SearchManager.ts#L495-L507);
  • PATH 2(Chroma 语义搜索)queryChroma 查询取回 100 条语义匹配 → 按 90 天新鲜度窗口(SEARCH_CONSTANTS.RECENCY_WINDOW_MS)或用户 dateRange 过滤 → 按 doc_type 分桶(observation / session_summary / user_prompt)→ 回 SQLite 水合完整行(SearchManager.ts#L356-L469)。整个路径包在 try/catch 中:捕获到异常时记录 chromaFailureReason(区分 ChromaUnavailableError 连接错误与其他错误),打 WARN 日志“falling back to FTS5 keyword search”,然后对 observations / sessions / prompts 三个集合分别调用 SessionSearch 的 FTS 搜索兜底(SearchManager.ts#L541-L563);
  • PATH 3(Chroma 未初始化,即 chromaSync 为 null):直接进入 FTS5 关键词搜索;连 FTS5 失败也只做 ERROR 日志而不再向上抛——保证搜索接口尽量不 500(SearchManager.ts#L565-L583)。

策略选择本身由 SearchOrchestrator.ts 的策略模式承担:构造 ChromaSearchStrategy / SQLiteSearchStrategy / HybridSearchStrategy,Chroma 查询失败时包装为 ChromaUnavailableError 上抛给上层,且对“按平台作用域的查询在 Chroma 中零命中”的场景自动回退 SQLite——这一分支正是为平台来源(platform source)隔离上线后旧元数据不含 platform_source 字段的情况准备的。

用户可感知的降级提示。 当回退后仍然零结果时,返回文本不是干巴巴的 "No results",而是 ResultFormatter.formatChromaFailureMessage:连接类错误提示“Semantic search is offline (Chroma MCP unreachable …)”并引导运行 /api/chroma/status?deep=1 诊断;其他错误引导查看 ~/.claude-mem/logs/ 中的 CHROMA_SYNC 日志。搜索链路同时通过遥测信封(SearchTelemetryEnveloperesult_countsearch_strategy: chroma|fts|filter_onlychroma_availablefallback_reason)把“这次搜索是否发生了静默降级”上报出来(SearchManager.ts#L590-L617),SearchRoutes.ts/api/search 中间件里统一采集 search_performed 事件——降级不再是黑盒,而是可度量的。

问题二:Chroma “Collection setup failed”(#1261、#1232)

手册给出的修复方案

手册判断用户报的 Collection 初始化失败“很可能由 Chroma MCP 子进程死亡或未启动引起”,要求阅读 src/services/sync/ChromaMcpManager.ts,围绕 collectionsetupcacheDir 三个关键词定位错误来源,并给出有韧性的初始化四步:

  1. collection 不存在则创建(幂等);
  2. 创建失败则延迟 2 秒重试一次;
  3. 重试仍失败则标记 Chroma 不可用(this.connected = false)并记录错误——SQLite 搜索保持可用
  4. 若根因是 Chroma 数据目录不存在,在启动 chroma-mcp 之前用 mkdirSync(path, { recursive: true }) 确保目录已创建。

当前仓库中的落地实现

ChromaMcpManager.ts 是目前该故障层最复杂的实现,对照手册的四条要求,当前代码的演进轨迹可以逐项印证:

数据目录递归创建(手册第 4 条)。 写入锁获取函数 acquireChromaWriterLock 在启动 chroma-mcp 之前先执行 fs.mkdirSync(normalizedDataDir, { recursive: true })ChromaMcpManager.ts#L438),随后以 wx 排他标志写入 .claude-mem-chroma-writer.lock。锁文件携带 pid / ownerId / dataDir / acquiredAt / startToken,处理三种竞争情形:锁不存在则创建;锁属于自己(重启后的同一进程)则接管;锁属于他人但进程已死(用 PID + 启动令牌双重校验 isChromaWriterLockLive)则清理过期锁重试;锁属于活进程则拒绝启动第二个 writer 并抛 ChromaUnavailableError。这保证了同一 Chroma 数据目录全局单 writer,避免两个 worker 同时写一个 persistent 目录导致的集合损坏。

标记不可用 + 降级保留 SQLite(手册第 3 条)。 管理器持有 private connected: boolean 标志;子进程意外关闭(transport.onclose)时立即 this.connected = false、注销 supervisor 注册、记录失败时间戳并进入 10 秒重连退避(RECONNECT_BACKOFF_MS = 10_000ChromaMcpManager.ts#L35)。上层 SearchManager 收到 ChromaUnavailableError 后即走 PATH 2 的 FTS5 回退或 PATH 3,搜索功能不中断——“SQLite 搜索保持可用”成为结构性保证而非临时补丁。

连接前的预检与预热。 每次连接先经过 prewarmChromaMcp:用 uvx --python 3.13 --with onnxruntime>=1.20 --with protobuf<7 --from chroma-mcp==0.2.6 chroma-mcp --help 跑一遍,目的是在真实 MCP 会话建立前把 uvx 环境构建完,默认超时 120 秒,可用环境变量/配置 CLAUDE_MEM_CHROMA_PREWARM_TIMEOUT_MS 调整(有效范围 1~600000 毫秒,ChromaMcpManager.ts#L26-L28ChromaMcpManager.ts#L606-L624)。两个依赖覆盖(onnxruntime>=1.20 保证能解析 all-MiniLM-L6-v2 的 pytorch-2.0 IR 模型;protobuf<7 避免 opentelemetry 生成文件被 protobuf 7.x 拒载)以运行时 --with 形式注入,无需 fork 上游 chroma-mcp。预热失败时以 spawn 时刻捕获的进程令牌做进程树清理,并抛 ChromaUnavailableError 进入上面的降级路径。

集合层面的探测。 “Collection 是否存在/为空”不再靠猜测,probeSemanticSearch() 分三阶段(connect → list → query)给出结构化诊断:先 chroma_list_collections 数集合,再对固定集合 cm__claude-mem 发一条 query_texts: ['ping'] 探针,失败时区分“collection 缺失/为空”与一般错误并返回 stagequeryLatencyMs 等字段(ChromaMcpManager.ts#L884-L936)——这与 formatChromaFailureMessage 提示用户执行的 /api/chroma/status?deep=1(由 ChromaRoutes.ts 提供服务)正好衔接:诊断入口在路由层,探测实现在管理器层。

子进程单例不变量。 手册没有展开但源码注释反复强调的一个事实:MCP SDK 的 transport.close() 只通知直接子进程(uvx),在 Linux 上孙进程(uv、python、chroma-mcp)会重新挂到 init 下存活,反复重连会累积 20+ 个实例。因此每一条放弃 transport 的路径(重连、传输错误、连接超时、onclose、stop)都必须经过 disposeCurrentSubprocess() 做“先优雅关闭(stdin EOF → 2s → SIGTERM → 2s → SIGKILL 升级由 SDK 完成),再以捕获的 PID 补进程树清扫”。这解释了为什么手册 Phase 07 的前置条件强调 Phase 06 的 Windows Chroma 修复——跨平台子进程生命周期本身就是 #1261/#1232 类问题的温床。

问题三:Worker 看似健康、MCP 实际断连(#1266)

手册给出的修复方案

用户报告的现象是:localhost:37777 的健康检查一直正常,但 MCP 工具全部停止工作。手册的诊断判断是:健康检查只验证了 HTTP 服务,没有验证 MCP 层,连接掉线后无人察觉。修复要求两条:

  1. /api/health 端点中加入 MCP 健康验证,健康响应包含 mcpConnected: true/false
  2. MCP 工具处理程序中,若 Worker 内部状态显示 Chroma 已断开,先尝试自动重连再返回错误。

当前仓库中的落地实现

健康探测的双层结构。 Worker 侧的 /api/health 是外部探针的入口:安装器(install.ts 中以 http://${workerUrlHost}:${workerPort}/api/health 轮询就绪)、doctor 命令(doctor.ts)、重启校验(restart-verify.ts 明确“只读取 pid 与 version 字段”,且 503 降级队列时仍返回可解析的响应体)以及守护式轮询 HealthMonitor.ts(注释即写明“/api/health 是最便宜的无扰动探针”)都依赖它。手册指出的盲区——HTTP 健康不代表 MCP/Chroma 链路健康——在源码中体现为把语义搜索链路的探测与 HTTP 存活探测解耦ChromaMcpManager.isHealthy() 通过真实调用 chroma_list_collections(limit 1)判定(ChromaMcpManager.ts#L872-L882),深探测用 probeSemanticSearch() 三阶段探针;这些结果经 Chroma 状态路由暴露,而 /api/health 保持轻量快速,避免把 uvx 子进程冷启动(可达分钟级)拖进每个部署探针的响应时间里。这一取舍与手册“在健康响应中报告 mcpConnected 布尔值”的方向一致:关键是被检方要显式暴露 MCP 层状态,而不是让 HTTP 200 隐含 MCP 正常

断连后的自动重连(手册第 2 条)。 callTool() 是所有 Chroma MCP 工具调用的收口:变更类工具(匹配 chroma_(add|create|delete|modify|update|upsert)_)在 local 模式下还会串行化入队并受 CLAUDE_MEM_CHROMA_MAX_PENDING_MUTATIONS(默认 5000)背压保护。核心的重连逻辑在 callToolUnqueuedChromaMcpManager.ts#L765-L806):工具调用抛出传输错误时,打 WARN 日志“reconnecting and retrying once”,先以捕获的进程令牌杀掉将死的子进程树(避免 Linux 上的孤儿泄漏),再 ensureConnected() 重建连接并重试一次;重试仍失败才把 connected 置 false 并抛出带工具名的错误。而 ensureConnected()ChromaMcpManager.ts#L150-L185)自身也具备防抖:距上次失败不足 10 秒直接抛“backoff (Ns remaining)”错误,并发调用共享同一个 connecting Promise,避免雪崩式重连。这就是手册设想的“Chroma 客户端查询中途断开 → 自动重连 → 仍失败才向用户返回错误”的完整闭环。

验证与回归:手册第四项任务

手册最后要求“运行测试并端到端验证搜索”,具体命令与当前仓库脚本的对应关系:

# 全量测试(package.json 中 test 脚本实际为 bun test tests)
npm test

# 构建并同步(package.json: build && sync-marketplace && restart-marketplace-worker)
npm run build-and-sync

# 端到端验证统一搜索端点(手册提示:按实际路由调整路径)
curl 'http://localhost:37777/api/search?q=test&limit=5'

需要注意两处与手册原文的出入:其一,package.jsontest 脚本的实际执行体是 bun test tests(另有 test:search 针对 tests/worker/search/ 的专项集),手册写作时期的 npm test 表述以当前仓库脚本为准;其二,手册 curl 示例中的 q/limit 参数与当前 SearchRoutes.ts 实际注册的路由完全吻合——统一搜索就是 GET /api/search,因此该命令在当前代码上可直接执行。若搜索发生降级,返回文本本身会携带 formatChromaFailureMessage 的诊断提示,配合 /api/chroma/status?deep=1 即可判断是“连接层(chroma_connection)”还是“查询层(chroma_error)”问题——这一判别维度直接来自 SearchTelemetryEnvelope.fallback_reason 的三个枚举值:chroma_connection / chroma_error / chroma_not_initializedSearchManager.ts#L28-L33)。

小结:搜索稳定性修复的设计模式

把 Phase 07 的三个问题放在一起看,当前代码呈现出一套一致的稳定性设计,可作为后续同类故障的排查模板:

  1. 错误不穿透,状态码有语义:路由基类统一 try/catch + 按 AppError 映射状态码,4xx/5xx 分级记日志(BaseRouteHandler.ts);
  2. 降级是显式路径,不是异常副产品:Chroma 语义 → FTS5 关键词的每条回退都在 SearchManager.search() 中有命名的代码路径,并由遥测信封记录策略与回退原因(SearchManager.ts#L590-L617);
  3. 子进程生命周期按不变量管理:全局单 writer 锁、spawn 时刻捕获的进程令牌、所有放弃连接的路径收口于一次树级清理,杜绝孤儿累积(ChromaMcpManager.ts);
  4. 不可用是正常状态connected = false + 10 秒退避 + 上层 FTS 兜底,让用户在语义搜索离线时依然拿到“结果可能不完整”的透明提示,而不是 500。

如需进一步深挖,可继续阅读 SearchOrchestrator.ts 的策略组合、ChromaSearchStrategy.ts 的检索细节、docs/public/architecture/search-architecture.mdx 的搜索架构文档,以及 ChromaMcpManager.ts 后半部分的停止/资源回收实现。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
983
503
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384