OpenRig 根因追踪法:沿调用链反向定位 Bug 的原始触发点
人工智能AI Agent多智能体Agent 编排代码智能体CLI【免费下载链接】openrigBuild your own network of agents from Claude Code, Codex and Pi: persistent teams with roles, shared context and owned work.项目地址https://gitcode.com/GitHub_Trending/op/openrig点击查看免费下载本篇技术指南基于 OpenRig 仓库内置的系统化调试技能systematic-debugging中的根因追踪文档 root-cause-tracing.md讲解如何在错误深埋于调用栈时通过反向追踪 堆栈插桩 污染者二分三条路径定位 Bug 的原始触发点并在源头修复后再叠加多层防御。读完后你将掌握一套可复制的调试方法论完整的五步追踪流程、堆栈埋点代码模板、测试污染源排查脚本find-polluter.sh的工作原理以及一个空字符串污染源码目录的真实案例全链路剖析。一、要解决的问题永远不要修在报错出现的地方OpenRig 的 agent 团队由 Claude Code、Codex 等角色组成每个角色按需加载一组技能skill来规范工作方式。根因追踪文档就是其中一个过程类技能process skill与 SKILL.md、defense-in-depth.md 同目录存放是系统化调试四阶段流程中阶段 1根因调查的第 5 步追踪数据流Trace Data Flow所显式指向的完整技术文档。文档开宗明义指出了常见误区Bug 经常出现在调用栈的深处——git init在错误的目录执行、文件被创建到错误位置、数据库以错误路径打开。你的本能反应是去修报错出现的地方但那只是在对症状下药。核心原则沿调用链反向追踪直到找到最初的触发点然后在源头修复。什么时候该用反向追踪原文档用一张决策图描述了触发条件其逻辑可以归纳为判断Bug 是否出现在调用栈深处——是则进入下一条判断判断能否向上反向追踪——能则追踪到原始触发点并且更进一步叠加纵深防御defense-in-depth不能追踪到死胡同才退而求其次在症状点修复。文档同时给出了四条明确的使用场景清单错误发生在执行深处而非入口点堆栈跟踪显示了很长的调用链不清楚非法数据从哪里产生需要找到是哪个测试/代码触发了问题。二、五步追踪流程含完整代码示例这是原文档的骨架下面完整继承其每一步并补充关键实现细节。第 1 步观察症状Observe the Symptom先原样记录报错例如Error: git init failed in /Users/jesse/project/packages/core注意报错中的路径往往就是最大的线索git init本不该在packages/core源码目录里执行。第 2 步找到直接原因Immediate Cause问哪段代码直接导致了这个结果await execFileAsync(git, [init], { cwd: projectDir });直接原因清楚了git init的cwd参数projectDir的值有问题。但这里还不是根因只是离症状最近的一层。第 3 步追问是谁调用了它What Called This?沿调用链一级一级向上列WorktreeManager.createSessionWorktree(projectDir, sessionId) → called by Session.initializeWorkspace() → called by Session.create() → called by test at Project.create()到这一步追踪对象从git init 为什么失败变成了projectDir这个值是怎么传进来的。第 4 步继续向上追问传进来的是什么值Keep Tracing UpprojectDir 一个空字符串 空字符串作为 cwd 会解析为 process.cwd() 而 process.cwd() 恰好就是源码目录这一步是整个方法的精髓追的不是哪行代码而是哪个值。execFileAsync对空cwd的兜底行为回落到process.cwd()解释了为什么git init落在了源码目录——症状与根因之间的桥梁就是参数的隐式语义。第 5 步找到原始触发点Find Original Trigger问这个空字符串是从哪来的const context setupCoreTest(); // 返回 { tempDir: } Project.create(name, context.tempDir); // 在 beforeEach 之前就被访问了根因浮出水面测试里一个顶层变量在模块加载时beforeEach尚未执行就读取了context.tempDir而此时它还是初始空值。三、无法手工追踪时注入堆栈跟踪当调用链太长、代码路径分支复杂肉眼追不动时文档给出的手段是在危险操作前插入插桩// 在问题操作之前 async function gitInit(directory: string) { const stack new Error().stack; console.error(DEBUG git init:, { directory, cwd: process.cwd(), nodeEnv: process.env.NODE_ENV, stack, }); await execFileAsync(git, [init], { cwd: directory }); }原文档对这段代码有三条硬约束值得逐条留意关键Critical测试里必须用console.error()而不是 logger——测试框架下 logger 可能被吞掉console.error写到 stderr 才保证可见在危险操作之前打日志而不是失败之后再补——失败后进程可能已经走偏事前的上下文才完整上下文要全目录、cwd、环境变量、完整调用链new Error().stack。运行并捕获调试输出npm test 21 | grep DEBUG git init拿到堆栈后按三个维度分析找测试文件名——问题出在哪个测试找触发调用的行号——测试内部哪一行发起的识别模式——是不是同一个测试、同一个参数反复出现。四、定位污染源find-polluter.sh 二分脚本原文档的另一个实操要点如果测试期间出现了不该存在的文件/状态比如源码目录里冒出了.git但你不知道是哪个测试干的就使用同目录的二分脚本./find-polluter.sh .git src/**/*.test.ts参数含义第一个参数是要检查的污染产物文件或目录路径第二个参数是测试文件的 glob 模式。脚本逐个运行测试、每次运行后检查污染产物是否出现在第一个制造污染者的测试处立即停下。这段使用说明在仓库中并非空口承诺——find-polluter.sh 是真实存在的可执行文件且带有一套严格的测试用例 find-polluter-script.test.ts。结合脚本源码它的行为契约比文档描述更细三种退出码脚本头部注释明确约定0 所有被测测试都成功且无污染1 找到污染者2 无结论输入非法、测试选择失败或存在未完成的测试运行拒绝无结论即通过测试文件列表为空、或find命令本身失败时直接以退出码2报 no tests ran / selection failed绝不假装检查通过对应测试 does not certify an empty selection、does not hide a discovery command failure;拒绝预存污染如果运行前污染产物已存在脚本直接退出并提示 Pollution already exists before testing不会继续跑测试对应测试 refuses preexisting pollution without running tests失败测试 ≠ 干净通过单个测试运行失败exit 7会被计入FAILED最终报告 Incomplete: N test runs failed; no polluter observed 并以退出码2结束避免把没观察到污染误报为全部干净文件名中的空格安全使用while IFS read -r逐行读取测试用例验证了one test.test.ts这类带空格路径的正确处理找到即停命中污染者后打印被测文件、污染产物详情ls -la并给出两条后续调查命令单独跑该测试、查看测试代码随即退出。[2/7] Testing: ./src/one.test.ts FOUND POLLUTER! Test: ./src/one.test.ts Created: .git To investigate: npm test ./src/one.test.ts # Run just this test cat ./src/one.test.ts # Review test code从脚本结构看它本质是一个顺序逐个执行 每轮状态断言的二分bisection变体牺牲了并行执行效率换取了第一个制造者的确定性归因。五、真实案例复盘一个空字符串污染了源码目录原文档给出了一个完整案例调试日期 2025-10-03五步流程在这里全部落地症状Symptom.git目录被创建在packages/core/源码目录里。追踪链Trace chaingit init运行在process.cwd()← 因为cwd参数为空WorktreeManager被传入空的projectDir调用Session.create()收到空字符串测试在beforeEach执行之前访问了context.tempDirsetupCoreTest()初始返回的就是{ tempDir: }。根因Root cause顶层变量初始化时机过早访问了尚为空的值。源头修复Fix把tempDir改成 getter——在beforeEach之前访问就直接抛异常让时序错误在最早的可能点显形而不是以空字符串的形式一路静默下传。同时叠加纵深防御defense-in-depth——四层校验与同目录 defense-in-depth.md 的四层模型一一对应层防御点作用Layer 1Project.create()校验目录入口拒绝明显非法输入非空/存在/可写Layer 2WorkspaceManager校验projectDir非空业务逻辑层兜底防不同代码路径绕过入口Layer 3测试环境守卫NODE_ENV guard拒绝在 tmpdir 之外执行git init环境级防呆防止特定上下文下的危险操作Layer 4git init前记录堆栈跟踪取证手段前三层都失效时仍可回溯defense-in-depth.md对此的总结是单点校验是我们修好了这个 Bug多层校验是我们让这个 Bug 变得不可能——不同层会捕获不同的绕过方式不同代码路径绕过入口校验、mock 绕过业务校验、跨平台边界情况需要环境守卫、调试日志暴露结构性误用。量化结果原文档记载通过 5 级追踪找到根因在源头修复getter 校验叠加 4 层防御1847 个测试全部通过、零污染。六、文档的核心原则与堆栈技巧速查原文档结尾给出了一条铁律对应其决策图中醒目的红色终止节点NEVER fix just where the error appears.Trace back to find the original trigger. 永远不要只修错误出现的地方。回溯找到原始触发点。配套的完整循环是找到直接原因 → 还能再往上一层追吗→ 能则继续反向追踪循环不能则视为被迫停在症状点 → 一旦确认到达源头修复源头 → 在每一层加校验 → 目标状态是让 Bug 在结构上不可能。堆栈跟踪技巧四条速查Stack Trace Tips测试中用console.error()而非 logger——logger 可能被抑制时机在危险操作之前记录而不是失败之后上下文目录、cwd、环境变量、时间戳抓栈new Error().stack显示完整调用链。七、这份文档在 OpenRig 中的位置与交付链路理解这份文档是谁写的、怎么被 agent 使用有助于把握其权威性边界1. 它是系统化调试技能的一个支撑技术文档。同目录的 SKILL.md 定义了整个先根因、后修复的四阶段流程根因调查 → 模式分析 → 假设与最小验证 → 实现其阶段 1 第 5 步明确写道当错误深埋调用栈时Seeroot-cause-tracing.mdin this directory for the complete backward tracing technique。该 SKILL.md 的 frontmatter 元数据声明此技能源自 Obra Superpowers 项目、以vendored-as-is原样引入方式 vendored最近一次上游一致性核对日期为 2026-05-13——也就是说文档内容与上游保持一致修改需走 vendoring 流程而非随意改写。2. 它是镜像真正的产品源在 daemon 内。按 skills/README.md 的说明skills/_canonical/是通过npm run mirror-skills实现见 mirror-skills.mjs从产品源packages/daemon/specs/agents/shared/skills/逐文件复制而来的公开镜像两者漂移会被mirror-skills --check检出并使测试失败。因此本仓库中 find-polluter.sh 与 root-cause-tracing.md 在packages/daemon/specs/agents/shared/skills/process/systematic-debugging/下都有同源副本。3. 技能内容随 CLI 一起打包分发。generate-context-packs.mjs 在构建期把技能内容投影为 context pack 条目输出到packages/daemon/context-packs/gitignored、每次构建重新生成保证运行时rig context get提供的字节与 CLI 版本天然一致。脚本注释特别强调技能引用的辅助资产点名了find-polluter.sh与condition-based-waiting-example.ts是镜像输出的一部分不能因后缀不被识别而被静默丢弃——generate-context-packs.test.mjs 中就有断言要求find-polluter.sh的存在及其完整内容必须出现在被服务的 bundle 里。换句话说这篇文档讲的那行./find-polluter.sh .git src/**/*.test.ts其依赖的脚本是随 npm 包真实下发、且内容受测试保障的。4. 它是渐进式披露progressive disclosure上下文注入的实例。按 skills/README 的描述技能 frontmatter 的descriptionUse when encountering any bug, test failure, or unexpected behavior, before proposing fixes是触发器驻留在 agent 的热层做廉价模式匹配正文在触发时加载同目录的引用文件如本文、find-polluter.sh按需加载。对使用者而言这意味着当 agent 面对 bug 时这套先根因调查、反向追踪、多层防御的流程会被自动唤起而不是依赖使用者记住某个调试手册。八、落地清单把这套方法带进自己的项目最小可执行清单是触发条件核对错误在调用栈深处 / 调用链很长 / 数据来源不明 / 不知哪个测试引发副作用——满足任一条就启用反向追踪而非就地打补丁五步追问症状 → 直接原因 → 谁调用了它 → 传进来的值是什么 → 值最初在哪里被赋值重点追值而不是行特别警惕空字符串、undefined这类会被下游隐式兜底的值如cwd: 回落到process.cwd();追不动就插桩在危险操作前用console.error记录参数 cwd 环境 new Error().stack跑一遍npm test 21 | grep DEBUG ...收集证据有状态污染就跑二分./find-polluter.sh 产物 测试glob注意它的退出码语义——2代表无结论预存污染、选择失败、有测试失败此时不应得出没污染的结论修复顺序先在源头修例如把过早访问改为 getter 抛错再按入口 → 业务 → 环境 → 取证日志四层补校验最后用完整测试套件验证。判断标准不是修好了而是这个 Bug 在结构上不可能了。赞分享人工智能AI Agent多智能体Agent 编排代码智能体CLI【免费下载链接】openrigBuild your own network of agents from Claude Code, Codex and Pi: persistent teams with roles, shared context and owned work.项目地址https://gitcode.com/GitHub_Trending/op/openrig点击查看免费下载相关推荐superpowers-zh 系统化调试之根因追踪沿调用链反向溯源在源头修复 Bugsuperpowers zh 系统化调试之根因追踪沿调用链反向溯源在源头修复 Bug 导读 本文是 superpowers zh 开源仓库中 systemaAI 技能AI 插件人工智能开发工具OpenRig 系统化调试沿调用链回溯根因的 root-cause-tracing 实战指南OpenRig 系统化调试沿调用链回溯根因的 root cause tracing 实战指南 导读 本指南以 OpenRig 仓库中 root cause t人工智能AI Agent多智能体Agent 编排代码智能体CLIQQ空间历史说说存档指南3步把历史说说完整归档到本地QQ空间历史说说存档指南3步把历史说说完整归档到本地 GetQzonehistory 是一个获取 QQ 空间历史说说的开源工具扫码登录到最终归档全程替你走网页爬虫数据分析上一篇老旧 Mac 多屏输出怎么做OpenCore Legacy Patcher 完整操作指南下一篇5 分钟配好手柄映射AntiMicroX 实战指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考