首页
/ claude-mem 错误处理反模式清理计划:132 项债务清单、检测器原理与零静默失败验收标准

claude-mem 错误处理反模式清理计划:132 项债务清单、检测器原理与零静默失败验收标准

2026-09-06 11:45:34作者:蔡怀权

本文以 claude-mem 仓库中的清理计划文档 docs/anti-pattern-cleanup-plan.md 为主体,完整还原这份"132 项错误处理反模式待修复"清单的组织方式与验收标准,并结合配套检测脚本 detect-error-handling-antipatterns.ts 的源码实现,讲解每类反模式的识别规则、已批准的覆盖(override)机制,以及如何在本地复现检测、确认 0 问题。读完本文,你可以理解该仓库如何用"检测器 + 债务清单 + 例外审批"三件套治理静默失败,并能把同一套方法移植到自己的 TypeScript 项目中。

计划背景:为什么要把 132 个反模式全部清掉

claude-mem 是一个跨会话持久记忆系统:它捕获 Agent 会话中的工具调用与观察结果,用 AI 压缩后注入未来会话的上下文。这类系统的核心链路(worker 守护进程、会话存储、搜索管理、向量同步)长期在后台运行,一旦发生静默吞错,故障可能几天后才以数据缺失的形式暴露。

计划文档开篇即给出总目标与执行入口:

  • 总任务量:132 个错误处理反模式待修复(Total: 132 anti-patterns to fix);
  • 检测器命令bun run scripts/anti-pattern-test/detect-error-handling-antipatterns.ts

这个"先全量扫描、逐项销账"的做法并非凭空而来。从 CHANGELOG.md 的 v8.5.3 记录可以看到其动因:一个过于宽泛的 try-catch 曾导致一次长达约 10 小时的调试,因为错误被静默吞掉、完全不可见;随后的版本将自动检测脚本引入仓库,并在 v8.5.4 中将其归类为"Error Handling Documentation & Tooling",明确用于识别空 catch 块、不规范的错误日志实践和过大的 try-catch 块。本文档则是这一治理过程进入"收尾清单"阶段的产物。

进度跟踪表:按文件分账的债务分布

计划文档的核心是一张 30 个文件的进度跟踪表(Progress Tracker),每个文件标注了待修复问题数。完整清单如下,可直接作为销账依据:

文件 问题数 文件 问题数
worker-service.ts 36 useContextPreview.ts 3
SearchManager.ts 28 SessionRoutes.ts 3
SessionStore.ts 18 ModeManager.ts 3
import-xml-observations.ts 7 context-generator.ts 3
ChromaSync.ts 6 useTheme.ts 2
BranchManager.ts 5 useSSE.ts 2
mcp-server.ts 5 usePagination.ts 2
logger.ts 3 SessionManager.ts 2
prompts.ts 2 useStats.ts 1
useSettings.ts 1
timeline-formatting.ts 1
paths.ts 1
SettingsDefaultsManager.ts 1
SettingsRoutes.ts 1
BaseRouteHandler.ts 1
SettingsManager.ts 1
SDKAgent.ts 1
PaginationHelper.ts 1
OpenRouterAgent.ts 1
GeminiAgent.ts 1
SessionQueueProcessor.ts 1

几个值得注意的结构特征:

  1. 头部集中:worker-service.ts(36)、SearchManager.ts(28)、SessionStore.ts(18)三个文件合计占 82/132,即约六成债务集中在守护进程服务、搜索与会话存储三条主链路上——这正是检测器中"关键路径"(CRITICAL PATHS)所覆盖的区域;
  2. 长尾为零风险点:其余 27 个文件多为 1~3 个问题,适合批量快速清理;
  3. 文档中的每一项都初始为未勾选- [ ]),意味着该计划是"先立账、后销账"的跟踪基线,修复过程中逐项打勾。

最终验收标准:0 问题、132 项覆盖保留、测试全绿

计划文档的 Final Verification 部分给出三条硬性验收标准:

  • 运行检测器并确认 0 issues(同时保留 132 个已批准覆盖,即 132 approved overrides remain);
  • 所有测试通过(仓库测试命令为 bun test tests,见 package.jsonscripts.test);
  • 提交变更。

"0 issues" 与 "132 个 approved overrides 保留"并存,正是这套治理体系的关键设计:检测器把代码中的可疑错误处理分成两档——

  • ISSUE:必须修复的真实反模式,存在任意一个就令脚本以退出码 1 失败(可强制进 CI);
  • APPROVED_OVERRIDE:经过人工审批、带书面理由的有意例外,脚本仍然报告它们(用于定期复核理由是否仍然成立),但不计入失败数。

验收标准最后还有一条 Notes:

All severity designators removed from detector - every anti-pattern is treated as critical. (检测器中已移除所有严重级标注——每个反模式都被视为 critical。)

也就是说,当前版本的检测器不再做"高危/中危"分级,一律按关键问题对待,这与 CHANGELOG.md 中"Enforces zero-tolerance policy for empty catch blocks"(对空 catch 零容忍)的基调一致。

检测器实现原理:十类反模式的识别规则

检测器源码位于 detect-error-handling-antipatterns.ts,使用 Bun 运行,从项目根目录递归扫描 src 下所有 .ts 文件(跳过 node_modulesdistplugin 和隐藏目录,见该脚本第 24-42 行的 findFilesRecursive 实现)。它包含两类分析机制:逐行正则匹配try/catch 块结构化分析

逐行正则匹配的四类反模式

以下四类在 detectAntiPatterns 函数中按行检查(源码第 60-150 行):

模式名 触发条件 检测器给出的理由
ERROR_STRING_MATCHING error.message / err.message / String(err).includes('...') 字符串匹配 通过字符串猜测错误类型既脆弱又掩盖真实错误;应记录完整错误对象。"We don't care about pretty error handling, we care about SEEING what went wrong."
PARTIAL_ERROR_LOGGING 只把 error.message 传给 logger.error/warn/info/debug/failureconsole.error/warn/log 只记 message 会丢失堆栈、错误类型和全部属性;必须传完整错误对象
ERROR_MESSAGE_GUESSING 同一行出现多个 .includes(...)|| 串联 用多重字符串检查猜测错误类型是"STOP GUESSING",应记录完整错误
PROMISE_EMPTY_CATCH .catch(() => {}) 空处理器 错误消失在虚空里

其中 ERROR_STRING_MATCHING 还带一个启发式:命中的匹配串若包含 errorfailconnectiontimeoutnotinvalidunable 等泛化词(源码第 70 行的 genericPatterns 列表),更说明该判断是在猜"大致是什么错"而不是精确处理某种已知异常。

Promise .catch 无日志检测

.catch(...) 处理器,检测器会用括号配平向前多看至多 10 行(源码第 177-203 行),收集 catch 回调体,若体内既没有 logger.error|warn|debug|info|failure 也没有 console.error|warn,则报 PROMISE_CATCH_NO_LOGGING。注意其注释中限定:只有当处理器确实是多行(lookAhead > 0)时才标记,避免误伤单行空函数体(那属于上一节的 PROMISE_EMPTY_CATCH)。

try/catch 块结构化分析:六类规则

analyzeTryCatchBlock(源码第 259-390 行)先用状态机找出 try 块与 catch 块的完整行范围(以 } 配平为界),再对 catch 体做如下检查:

  1. EMPTY_CATCH:catch 体去掉注释后为空。描述直接点明后果:"User will waste hours debugging."(用户要浪费数小时调试)。
  2. NO_LOGGING_IN_CATCH:catch 体既无 logger.*、也无 console.error|warn、无 process.stderr.write、也没有 throw。若该 catch 内存在 // [ANTI-PATTERN IGNORED]: 理由 注释,则降为已批准覆盖(见下一节)。
  3. LARGE_TRY_BLOCK:try 体中"有效行"(非空、非纯注释、非单独花括号)超过 10 行,判定为作用域过宽——多种错误被笼统归入同一个 catch,无法区分具体失败点。
  4. GENERIC_CATCH:catch 捕获了具名参数,但块内没有任何错误类型区分(不含 instanceof Errorerr.name ===typeof ... === 'object' 检查)。
  5. CATCH_AND_CONTINUE_CRITICAL_PATH:仅对关键路径文件触发。当 catch 块有日志、既没有 throw 也没有 return/process.exit(即记完日志后照常继续执行)时触发——在关键路径上"记日志然后继续"可能导致静默数据损坏。

关键路径(CRITICAL PATHS)名单

脚本第 16-22 行定义了五个关键路径文件,文件名命中即视为 critical:

const CRITICAL_PATHS = [
  'ClaudeProvider.ts',
  'GeminiProvider.ts',
  'OpenRouterProvider.ts',
  'SessionStore.ts',
  'worker-service.ts'
];

这与 CHANGELOG.md v8.5.3 中"Critical Path Protection"一节列出的受"严格错误传播(禁止 catch-and-continue)"保护的文件相互印证(Agent 实现文件在该版本记录中写作 SDKAgent.ts / GeminiAgent.ts / OpenRouterAgent.ts,脚本当前以 Provider 命名匹配)。从源码结构看,这份名单对应的是会话摘要生成链路上的模型 Provider 与持久化层——一旦这些位置吞错,压缩结果或存储数据会在无人察觉的情况下出错。

输出报告与退出码

formatReport(源码第 392-449 行)输出一份终端报告:先汇总 Found N anti-patterns that must be fixedAPPROVED OVERRIDES 计数,再分别列出每个问题的 文件:行号 - 模式名 及说明,以及每个已批准覆盖的 Reason: 与最多 3 行代码片段。报告尾部固定附一段"每个 try-catch 必须回答的五个问题":

  1. 我捕获的是哪个具体错误?(说出名字)
  2. 给我文档证明这个错误可能发生;
  3. 为什么这个错误无法被预防?
  4. catch 块要做什么?(记录 + 重抛?回退?)
  5. 为什么这个错误不应该传播给调用方?

最后给出豁免语法提示:// [ANTI-PATTERN IGNORED]: reason。脚本主流程在扫描结束后统计 ISSUE 数量:大于 0 则打印 FAILED: N error handling anti-patterns must be fixed. 并以 退出码 1 结束;否则以 退出码 0 结束(源码第 469-475 行)。这正是它可以在 CI 中作为强制门禁的原因——存在任何未修复反模式即构建失败。

已批准覆盖机制:[ANTI-PATTERN IGNORED] 注释如何生效

检测器识别豁免的规则很直接(源码第 55-58 行):被检查行本身、或其紧邻上一行包含 [ANTI-PATTERN IGNORED] 文本时视为有豁免,并用正则 \[ANTI-PATTERN IGNORED\]:\s*(.+) 提取冒号后的理由作为 overrideReason。带豁免的命中项被标记为 APPROVED_OVERRIDE 而非 ISSUE:仍会被报告、要求复核理由,但不触发失败退出。对 try/catch 块的分析同样支持块内 // [ANTI-PATTERN IGNORED]: ... 注释(源码第 298-299 行)。

当前仓库中实际存在的豁免写法可作为范本:

  • ChromaSync.ts:两行对 ECONNREFUSED / ENOTFOUND 的字符串匹配各带独立豁免,理由说明 MCP 传输层会把底层错误重新包装成普通 Error、结构化 code 字段已丢失,因此只能比对消息文本,且完整错误对象会在下方被记录——这解释了为何"字符串匹配"在此处是不得已而为之的合理选择;
  • CodexCliInstaller.tswhere/which 探测命令不在 PATH 时必然非零退出,这本身就是预期的负向探测结果,因此空 catch 属合理;
  • parser.ts:tree-sitter grammar 包未安装对不支持的语言属于预期情况,调用方会回退到用户 grammar 或无符号折叠视图;
  • install.ts:多处豁免理由统一强调"失败已通过交互式感知的 log.warn 包装器告知用户,此处再直接 console 输出会造成双重打印"。

这些实例展示了豁免理由应有的形态:说明为什么常规规则在此处不适用(错误是预期信号、信息已由更合适的通道输出、或底层库丢掉了结构化字段),而不是笼统地"性能原因忽略"。另注意 CHANGELOG.md v8.5.3 中早期版本使用的标记是 // [APPROVED OVERRIDE]:,而当前检测器只识别 [ANTI-PATTERN IGNORED]——从版本演进看,标记文本已迭代过一次,以当前脚本为准。

如何在本地复现检测与销账流程

整个流程只依赖 Bun(CLAUDE.md 说明该仓库要求 Bun,缺失时自动安装):

# 1. 在仓库根目录运行检测器
bun run scripts/anti-pattern-test/detect-error-handling-antipatterns.ts

# 2. 查看报告:
#    - "Found N anti-patterns that must be fix" 之后逐条列出 文件:行号 - 模式名
#    - 退出码 1 = 仍有未修复反模式;退出码 0 = 达标

# 3. 修复过程中对每个命中项二选一:
#    a) 重构代码消除反模式(补全量错误日志、缩小 try 作用域、区分错误类型、关键路径上 throw/return);
#    b) 确属合理例外,在被标记行或其上一行写 // [ANTI-PATTERN IGNORED]: <技术理由>

# 4. 全部销账后按计划文档的 Final Verification 验收:
bun test tests   # 所有测试通过
git commit       # 提交变更

修复时的判断可以参照检测器报告尾部内置的"五问检查单",其核心立场非常鲜明:宁可丑陋地暴露完整错误,也不要精致地猜测错误——记录完整错误对象(含堆栈与全部属性)是默认正确做法;LARGE_TRY_BLOCK(>10 有效行)提示需要拆分 try 作用域以便定位具体失败点;关键路径文件上的 catch 必须以 throwreturnprocess.exit 终止当前执行流,不允许"记日志后继续"。

历史脉络:从 163 项到 132 项的治理轨迹

把计划文档放回 CHANGELOG.md 的时间线中可以完整还原这场清理的三个阶段:

  1. v8.5.3(2025-12-31,"Error Handling Hardening"):由一次 10 小时调试事故触发,引入检测脚本与 CLAUDE.md 错误处理标准;按波次修复——Wave 1 清 5 个文件的空 catch(import-xml-observations.ts、bun-path.ts、cursor-utils.ts、worker-utils.ts 等),Wave 2 处理 8 处关键路径上的 Promise catch(worker-service.ts、SDKAgent.ts、GeminiAgent.ts、OpenRouterAgent.ts、SessionManager.ts),Wave 3 对 29 个 catch 块做全面审计(16 处补日志、13 处补书面豁免理由)。该版本记录的关键数据为:163 个反模式 → 26 个已批准覆盖,静默失败减少 84%
  2. v8.5.4(2026-01-02):将检测脚本正式列入"Error Handling Documentation & Tooling",并落地 8 个核心服务(BranchManager、PaginationHelper、SDKAgent、SearchManager、paths.ts、timeline-formatting、transcript-parser、ChromaSync)的日志补强——其中 BranchManager、SearchManager、paths.ts、timeline-formatting 均出现在计划文档的待修清单中,说明 v8.5.4 的补强针对的是"有日志但仍是反模式形态"的部分,剩余问题留待本计划逐项处理;
  3. 本计划文档:以 132 项为剩余债务的总账,按文件列出明细,约定"0 issues + 132 项覆盖保留 + 测试全绿 + 提交"为完成标准,并把检测器统一为"所有反模式皆为 critical"的零容忍模式。

从源码结构看,检测脚本被放在 scripts/anti-pattern-test/ 子目录且未挂到 package.jsonscripts 字段中,说明它是以"按需运行 + 退出码语义"方式在本地/CI 中被显式调用的治理工具,而非每次构建的自动步骤。

小结

这份清理计划文档的价值不在于清单本身,而在于它呈现了一套可复用的错误处理治理闭环:检测器给出客观基线(132 项)→ 按文件分账逐项销账 → 合理例外必须以带技术理由的 [ANTI-PATTERN IGNORED] 注释显式登记 → 以"0 issues + 覆盖数守恒 + 测试全绿"作为可验证的完成标准。检测器十类反模式的规则、五问检查单、关键路径名单和退出码门禁都沉淀在 detect-error-handling-antipatterns.ts 中,可直接借鉴到任何 TypeScript 长驻服务项目的错误处理审计中。

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