根因追溯Skill root-cause-tracing

根因追溯是软件调试中的一个核心技能,用于在错误发生时通过系统回溯调用栈、添加日志等工具来找到问题的根本原因,帮助识别无效数据或不正确行为的源头,适用于测试和开发过程中的错误追踪和预防。关键词包括:根因追溯、软件调试、错误追踪、调用栈、日志记录、测试自动化、深度防御、bug修复。

测试 0 次安装 0 次浏览 更新于 3/24/2026

name: root-cause-tracing description: 在错误发生在执行深处且需要追溯以找到原始触发点时使用 - 系统地通过调用栈向后追踪bug,必要时添加日志,以识别无效数据或不正确行为的源头

根因追溯

概述

Bug 经常在调用栈深处显现(例如在错误目录中执行 git init,文件创建在错误位置,数据库以错误路径打开)。你的直觉是在错误出现的地方修复,但这只是治标不治本。

核心原则: 通过调用链向后追溯,直到找到原始触发点,然后在源头修复。

何时使用

digraph when_to_use {
    "Bug appears deep in stack?" [shape=diamond];
    "Can trace backwards?" [shape=diamond];
    "Fix at symptom point" [shape=box];
    "Trace to original trigger" [shape=box];
    "BETTER: Also add defense-in-depth" [shape=box];

    "Bug appears deep in stack?" -> "Can trace backwards?" [label="yes"];
    "Can trace backwards?" -> "Trace to original trigger" [label="yes"];
    "Can trace backwards?" -> "Fix at symptom point" [label="no - dead end"];
    "Trace to original trigger" -> "BETTER: Also add defense-in-depth";
}

使用时机:

  • 错误发生在执行深处(非入口点)
  • 堆栈跟踪显示长调用链
  • 不清楚无效数据源自何处
  • 需要找出哪个测试/代码触发了问题

追踪过程

1. 观察症状

Error: git init failed in /Users/jesse/project/packages/core

2. 找到直接原因

是什么代码直接导致了这个?

await execFileAsync('git', ['init'], { cwd: projectDir });

3. 询问:什么调用了这个?

WorktreeManager.createSessionWorktree(projectDir, sessionId)
  → called by Session.initializeWorkspace()
  → called by Session.create()
  → called by test at Project.create()

4. 继续向上追溯

传递了什么值?

  • projectDir = '' (空字符串!)
  • 空字符串作为 cwd 解析为 process.cwd()
  • 那就是源代码目录!

5. 找到原始触发点

空字符串来自哪里?

const context = setupCoreTest(); // Returns { tempDir: '' }
Project.create('name', context.tempDir); // 在beforeEach之前访问!

添加堆栈跟踪

当无法手动追踪时,添加日志:

// 在问题操作之前
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 });
}

关键点: 在测试中使用 console.error()(而不是日志器 - 可能不会显示)

运行并捕获:

npm test 2>&1 | grep 'DEBUG git init'

分析堆栈跟踪:

  • 寻找测试文件名
  • 找到触发调用的行号
  • 识别模式(相同测试?相同参数?)

找出哪个测试导致污染

如果在测试期间出现某些东西但不知道是哪个测试:

使用二分脚本:@find-polluter.sh

./find-polluter.sh '.git' 'src/**/*.test.ts'

逐个运行测试,在第一个污染者处停止。参见脚本使用说明。

真实示例:空 projectDir

症状: .gitpackages/core/ 中创建(源代码目录)

追溯链:

  1. git initprocess.cwd() 中运行 ← 空的 cwd 参数
  2. WorktreeManager 以空 projectDir 调用
  3. Session.create() 传递空字符串
  4. 测试在 beforeEach 之前访问 context.tempDir
  5. setupCoreTest() 最初返回 { tempDir: '' }

根因: 顶层变量初始化访问空值

修复: 使 tempDir 成为 getter,如果在 beforeEach 之前访问则抛出异常

同时添加深度防御:

  • 层1:Project.create() 验证目录
  • 层2:WorkspaceManager 验证非空
  • 层3:NODE_ENV 防护拒绝在 tmpdir 外执行 git init
  • 层4:在 git init 前添加堆栈跟踪日志

关键原则

digraph principle {
    "Found immediate cause" [shape=ellipse];
    "Can trace one level up?" [shape=diamond];
    "Trace backwards" [shape=box];
    "Is this the source?" [shape=diamond];
    "Fix at source" [shape=box];
    "Add validation at each layer" [shape=box];
    "Bug impossible" [shape=doublecircle];
    "NEVER fix just the symptom" [shape=octagon, style=filled, fillcolor=red, fontcolor=white];

    "Found immediate cause" -> "Can trace one level up?";
    "Can trace one level up?" -> "Trace backwards" [label="yes"];
    "Can trace one level up?" -> "NEVER fix just the symptom" [label="no"];
    "Trace backwards" -> "Is this the source?";
    "Is this the source?" -> "Trace backwards" [label="no - keeps going"];
    "Is this the source?" -> "Fix at source" [label="yes"];
    "Fix at source" -> "Add validation at each layer";
    "Add validation at each layer" -> "Bug impossible";
}

切勿只修复错误出现的地方。 回溯找到原始触发点。

堆栈跟踪技巧

在测试中: 使用 console.error() 而不是日志器 - 日志器可能被抑制 操作前: 在危险操作前记录,而不是失败后 包含上下文: 目录、cwd、环境变量、时间戳 捕获堆栈: new Error().stack 显示完整调用链

实际影响

来自调试会话(2025-10-03):

  • 通过5级追溯找到根因
  • 在源头修复(getter验证)
  • 添加了4层防御
  • 1847个测试通过,零污染