关于 debugging-difficult-bugs
面向中高难度缺陷的调试技能,要求观测真实运行路径、复现问题并分析 JSONL 日志,再据此修复。适用于测试无法充分代表实际运行行为的情况。
主要功能
- 01在运行路径添加临时日志
- 02以 JSONL 记录状态与分支
- 03复现并分析真实问题
- 04依据日志定位首次偏差
- 05修复后清理日志并补回归测试
适用场景
- 01排查跨函数或进程缺陷
- 02调查并发、缓存或持久化问题
- 03验证手动复现的 UI 或运行时问题
- 04避免基于不完整测试推测修复
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ mastra-ai/ mastra --skill debugging-difficult-bugs安装后在 Claude Code 中输入/debugging-difficult-bugs确认已加载。
详细介绍
调试疑难 Bug
在中等或困难的 bug 上尽早使用此技能。这类情况下,常规 TDD 可能会带来虚假的信心,因为测试并未完整捕捉真实的 bug。
核心思路:对真实运行时路径进行埋点,复现真实问题,然后在决定修复方案之前检查只追加(append-only)的 JSONL 日志。
何时使用
当以下任一情况成立时,在调试初期使用此工作流:
- Bug 属于中等或困难复杂度,尤其是跨越多个函数、包、进程或 UI/运行时边界时。
- 测试为红色,但失败的测试可能并不完整地模拟了真实的 bug。
- 你想在没有新的运行时证据的情况下进行第二次推测性修复。
- Bug 依赖于运行时顺序、状态、缓存、流式处理、并发、持久化、UI 交互或外部服务。
- 用户表示自己可以手动复现该问题。
- 修改后测试通过,但你对它是否证明了所报告的实际 bug 已修复并不确定。
如果你不理解运行时行为,不要仅仅持续迭代测试。
只有当根因已被堆栈跟踪或能够执行真实运行时路径的确定性失败测试直接证明时,才可跳过此工作流。如果你想进行第二次推测性修复,请使用此工作流。
必要步骤
-
说明不确定性
- 承认当前测试可能未能捕捉实际的 bug。
- 确定必须观察的真实代码路径。
-
添加临时的无条件埋点
- 在可疑的代码流程中添加最少但足够的日志。
- 记录边界、有意义的分支决策、状态转换、异步顺序节点、返回值以及被捕获的错误;不要记录每一行。
- 日志必须是无条件的:不要将其置于环境变量、调试标志或日志级别之后。
- 每个日志点必须向当前工作目录下的
.jsonl文件追加一个 JSON 对象(每行一个)。 - 包含足够的上下文以重建路径:事件名称、时间戳、相关 ID、输入结构、状态转换、分支决策、返回值以及被捕获的错误。
-
复现真实问题
- 如果可能,优先自行运行复现过程。
- 如果问题需要用户的环境或手动交互,请在添加埋点后请用户复现。
- 明确告诉用户需要发送哪个
.jsonl文件,或请他们在复现完成后告知你,以便你检查日志。
-
在修复前分析日志
- 按时间顺序阅读 JSONL 日志。
- 对比预期流程与实际流程。
- 找出状态或行为首次出现偏离的位置。
- 只有在此之后才实施修复。
-
清理埋点
- 在理解根因并验证修复后,移除所有临时的无条件日志。
- 移除调试导入、辅助函数、生成的
.jsonl文件以及其他临时产物。 - 检查最终 diff 中是否残留埋点内容。
- 除非用户明确要求,不要在最终 diff 中保留调试文件、日志辅助函数或嘈杂的运行时日志。
-
保留或完善测试
- 在理解真实 bug 后,添加或调整一个聚焦的回归测试。
- 让测试断言从日志中发现的实际错误行为,而不是之前错误的假设。
JSONL 日志模式
在 cwd 中使用只追加的 JSONL,使其适用于各类 CLI、开发服务器、测试以及手动复现。
Node / TypeScript
import { appendFileSync } from 'node:fs';
import { join } from 'node:path';
function debugBug(event: string, data: Record<string, unknown> = {}) {
appendFileSync(
join(process.cwd(), 'debug-difficult-bug.jsonl'),
`${JSON.stringify({
ts: new Date().toISOString(),
event,
...data,
})}\n`,
);
}
在每个有意义的分支或状态转换处调用它:
debugBug('workflow.start', { runId, stepId, inputKeys: Object.keys(input ?? {}) });
debugBug('workflow.beforeStep', {
runId,
stepId,
status: step.status,
hasResumeData: Boolean(resumeData),
});
try {
const result = await executeStep();
debugBug('workflow.afterStep', { runId, stepId, resultShape: Object.keys(result ?? {}) });
return result;
} catch (error) {
debugBug('workflow.stepError', {
runId,
stepId,
errorName: error instanceof Error ? error.name : typeof error,
errorMessage: error instanceof Error ? error.message : String(error),
});
throw error;
}
记录什么
优先使用紧凑的结构化数据,避免倾倒大段内容。
应记录:
- 函数或阶段名称。
- 稳定的 id:请求 id、运行 id、线程 id、资源 id、步骤 id、工具调用 id。
- 输入/输出的结构:键、数量、长度、状态。
- 分支决策及导致该决策的数据。
- 变更前后的状态。
- 错误名称/消息及相关元数据。
- 异步、流式或并发流程中的顺序标记。
避免记录:
- API 密钥、认证头、令牌、Cookie、凭据。
- 完整的用户内容,除非必要且安全。
- 会使日志难以阅读的大型负载。
- 二进制数据或完整的模型响应,除非 Bug 确实需要。
请将调试日志视为可能包含敏感信息。除非用户已经审阅并脱敏,否则不要请用户将其粘贴到公开的 issue、PR 或共享频道中。
如果可能出现敏感数据,请记录脱敏后的摘要:
debugBug('request.received', {
hasAuthHeader: Boolean(headers.authorization),
bodyKeys: Object.keys(body ?? {}),
messageCount: body?.messages?.length,
});
向用户交接复现步骤
当用户需要手动复现时,请严格按照以下格式说明:
I added temporary unconditional JSONL instrumentation. Please reproduce the issue once, then send me or point me at:
<cwd>/debug-difficult-bug.jsonl
After I inspect that log, I’ll remove the instrumentation and make the actual fix.
如果多个进程的工作目录不同,可以选择以下任一方式:
- 在启动时记录绝对路径
process.cwd()、进程角色和 pid。 - 写入不同的文件,例如
debug-server-flow.jsonl、debug-worker-flow.jsonl和debug-client-flow.jsonl。
分析检查清单
在编写修复方案之前,请回答以下问题:
- 被插桩的代码路径是否确实执行了?
- 预期的事件顺序是什么?
- 实际的事件顺序是什么?
- 第一个错误的状态、缺失的值、重复的事件或错误的分支是什么?
- 最初失败的测试是否准确捕捉到了这一差异?
- 如果没有,回归测试应如何修改?
最终验证
只有满足以下条件,困难的 Bug 才算修复完成:
- 真实的复现路径通过。
- 在可行的情况下,回归测试在修复前失败、修复后通过。
- 临时的无条件插桩代码已被移除。
- 最终 diff 仅包含修复代码和有意添加的测试。
- 你能够依据 JSONL 日志中的证据解释根本原因。
评论
支持 Markdown