claude-mem 错误处理反模式清理计划:132 项债务清单、检测器原理与零静默失败验收标准
本文以 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 |
几个值得注意的结构特征:
- 头部集中:worker-service.ts(36)、SearchManager.ts(28)、SessionStore.ts(18)三个文件合计占 82/132,即约六成债务集中在守护进程服务、搜索与会话存储三条主链路上——这正是检测器中"关键路径"(CRITICAL PATHS)所覆盖的区域;
- 长尾为零风险点:其余 27 个文件多为 1~3 个问题,适合批量快速清理;
- 文档中的每一项都初始为未勾选(
- [ ]),意味着该计划是"先立账、后销账"的跟踪基线,修复过程中逐项打勾。
最终验收标准:0 问题、132 项覆盖保留、测试全绿
计划文档的 Final Verification 部分给出三条硬性验收标准:
- 运行检测器并确认 0 issues(同时保留 132 个已批准覆盖,即
132 approved overrides remain); - 所有测试通过(仓库测试命令为
bun test tests,见 package.json 的scripts.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_modules、dist、plugin 和隐藏目录,见该脚本第 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/failure 或 console.error/warn/log |
只记 message 会丢失堆栈、错误类型和全部属性;必须传完整错误对象 |
ERROR_MESSAGE_GUESSING |
同一行出现多个 .includes(...) 用 || 串联 |
用多重字符串检查猜测错误类型是"STOP GUESSING",应记录完整错误 |
PROMISE_EMPTY_CATCH |
.catch(() => {}) 空处理器 |
错误消失在虚空里 |
其中 ERROR_STRING_MATCHING 还带一个启发式:命中的匹配串若包含 error、fail、connection、timeout、not、invalid、unable 等泛化词(源码第 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 体做如下检查:
EMPTY_CATCH:catch 体去掉注释后为空。描述直接点明后果:"User will waste hours debugging."(用户要浪费数小时调试)。NO_LOGGING_IN_CATCH:catch 体既无logger.*、也无console.error|warn、无process.stderr.write、也没有throw。若该 catch 内存在// [ANTI-PATTERN IGNORED]: 理由注释,则降为已批准覆盖(见下一节)。LARGE_TRY_BLOCK:try 体中"有效行"(非空、非纯注释、非单独花括号)超过 10 行,判定为作用域过宽——多种错误被笼统归入同一个 catch,无法区分具体失败点。GENERIC_CATCH:catch 捕获了具名参数,但块内没有任何错误类型区分(不含instanceof Error、err.name ===或typeof ... === 'object'检查)。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 fixed 与 APPROVED OVERRIDES 计数,再分别列出每个问题的 文件:行号 - 模式名 及说明,以及每个已批准覆盖的 Reason: 与最多 3 行代码片段。报告尾部固定附一段"每个 try-catch 必须回答的五个问题":
- 我捕获的是哪个具体错误?(说出名字)
- 给我文档证明这个错误可能发生;
- 为什么这个错误无法被预防?
- catch 块要做什么?(记录 + 重抛?回退?)
- 为什么这个错误不应该传播给调用方?
最后给出豁免语法提示:// [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.ts:
where/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 必须以 throw、return 或 process.exit 终止当前执行流,不允许"记日志后继续"。
历史脉络:从 163 项到 132 项的治理轨迹
把计划文档放回 CHANGELOG.md 的时间线中可以完整还原这场清理的三个阶段:
- 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%; - 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 的补强针对的是"有日志但仍是反模式形态"的部分,剩余问题留待本计划逐项处理;
- 本计划文档:以 132 项为剩余债务的总账,按文件列出明细,约定"0 issues + 132 项覆盖保留 + 测试全绿 + 提交"为完成标准,并把检测器统一为"所有反模式皆为 critical"的零容忍模式。
从源码结构看,检测脚本被放在 scripts/anti-pattern-test/ 子目录且未挂到 package.json 的 scripts 字段中,说明它是以"按需运行 + 退出码语义"方式在本地/CI 中被显式调用的治理工具,而非每次构建的自动步骤。
小结
这份清理计划文档的价值不在于清单本身,而在于它呈现了一套可复用的错误处理治理闭环:检测器给出客观基线(132 项)→ 按文件分账逐项销账 → 合理例外必须以带技术理由的 [ANTI-PATTERN IGNORED] 注释显式登记 → 以"0 issues + 覆盖数守恒 + 测试全绿"作为可验证的完成标准。检测器十类反模式的规则、五问检查单、关键路径名单和退出码门禁都沉淀在 detect-error-handling-antipatterns.ts 中,可直接借鉴到任何 TypeScript 长驻服务项目的错误处理审计中。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00