六阶段 Bug 诊断循环:用「紧致反馈回路」驯服顽固 Bug 与性能回归 —— mattpocock-skills 的 diagnosing-bugs 技能实战指南
【免费下载链接】skillsSkills for Real Engineers. Straight from my .agents directory.项目地址: https://gitcode.com/GitHub_Trending/skills13/skills
导读:
diagnosing-bugs是本仓库(mattpocock-skills)内置的一套模型自动触发的六阶段 Bug 诊断技能,面向那些一眼看不透的顽固缺陷、偶发 flake 和夹在两个已知正常状态之间的性能回归。它的核心主张只有一条:在获得一个"紧致"的反馈回路之前,禁止任何理论推断——先构建一个能针对当前 Bug 变红、修复后变绿的单一命令,后续的二等分、假设检验、插桩全部只是机械地消耗这个信号。读完本文,你将掌握这套技能的触发时机、十条反馈回路构建阶梯、六个阶段之间的门禁规则、hitl-loop.template.sh人机协作脚本的完整用法,以及它与triage、improve-codebase-architecture等相邻技能的分工边界。
一、技能定位:它做什么,不做什么
diagnosing-bugs对一个顽固 Bug 或性能回归执行六阶段诊断:构建复现(build a repro)→ 最小化(minimise)→ 假设排序(rank hypotheses)→ 插桩(instrument)→ 带回归测试的修复(fix with a regression test)→ 清理(clean up)。
它最重要的设计决策是禁止先入为主:在存在一个紧致反馈回路之前,它不会让 Agent 形成任何理论。所谓紧致回路,指的是一个具名的命令,且已经实际运行过至少一次,它能在这个Bug 上变红、并在修复后变绿。默认情况下,拿到 Bug 报告的编码 Agent 的行为是"读代码、猜原因",而本技能恰恰要阻断这种行为——如果不存在一个能变红的命令,就没有 Phase 2。这一个门禁就是技能存在的全部意义:一旦信号存在,其后的二等分(bisection)、假设检验、插桩都只是机械工作。
何时使用(含适用场景速查表)
用户输入/diagnosing-bugs,或者任务匹配时 Agent 自动触发。它是**模型触发(model-invoked)**的技能,会响应"diagnose / debug this"或"某东西坏了、抛异常、失败了、变慢了"这类报告。对应的元数据可以在 agents/openai.yaml 中看到:
interface: display_name: "Diagnosing Bugs" short_description: "Diagnose hard bugs and regressions"以及 SKILL.md 的 frontmatter:
--- name: diagnosing-bugs description: Diagnosis loop for hard bugs and performance regressions. Use when the user says "diagnose"/"debug this", or reports something broken/throwing/failing/slow. ---应该在难题上使用它:一眼看不透的 Bug、偶发 flake、夹在两个已知正常状态之间的回归。它天生偏重,对于"一条消息就能回答"的问题属于杀鸡用牛刀。原文档给出了清晰的情境路由表:
| 你的处境 | 该去哪里 |
|---|---|
| 一个你能描述出具体症状的缺陷 | 本技能 |
| 一个慢端点,或已知 before-and-after 的时序回归 | 本技能:它有性能分支(先测基线,再二分) |
| "这个代码库的瓶颈在哪里?",没有具体症状 | 不是本技能。它诊断一个已知的失败,不做审计 |
| 别人发来的原始 Bug 报告,尚未确认或整理 | 先用 triage |
| 回答设计问题的临时代码,而非追踪缺陷 | prototype |
| 用测试先行构建一个有规划的行为 | tdd |
| 没有合适的缝(seam)来锁定 Bug | improve-codebase-architecture:本技能会主动向它交接 |
二、紧致回路(The Tight Loop)才是技能本身
Phase 1 得到了不成比例的投入,因为它是唯一困难的阶段。技能给出了一条构建回路的阶梯,按偏好程度大致排列:
- 失败的测试:在能触达 Bug 的任何缝(unit / integration / e2e)上写一个。
- Curl / HTTP 脚本:对着一个运行中的 dev server 打请求。
- CLI 调用:带 fixture 输入,将 stdout 与已知正确的快照做 diff。
- 无头浏览器脚本(Playwright / Puppeteer):驱动 UI,对 DOM / console / network 断言。
- 重放捕获的痕迹:把真实的网络请求 / payload / 事件日志存盘,在隔离环境中沿代码路径重放。
- 一次性 harness:拉起系统的最小子集(单个服务、mock 依赖),用一次函数调用触发 Bug 代码路径。
- 属性 / 模糊测试循环:如果 Bug 是"有时输出错误",跑 1000 个随机输入寻找失败模式。
- 二等分 harness:如果 Bug 出现在两个已知状态(commit、数据集、版本)之间,把"在状态 X 启动、检查、重复"自动化,交给
git bisect run。 - 差分循环:同一输入分别跑旧版本 vs 新版本(或两套配置),diff 输出。
- 人机协作 bash 脚本:最后手段。仓库为此配套了 scripts/hitl-loop.template.sh——Agent 运行脚本,你在终端里按提示操作,你的回答以可解析的输出回流给 Agent。
有一个回路不是目标,"紧致"才是。紧致意味着:
- 快(fast):秒级,而不是分钟级;
- 确定性(deterministic):每次运行结论一致;
- 锐利(sharp):断言的是你的确切症状,而不是"没崩溃";
- 可无人值守运行(agent-runnable):Agent 可以无人看管地反复运行它,人只有在 HITL 脚本场景才介入。
一个 30 秒的 flaky 回路比没有好不了多少;一个 2 秒的确定性回路才是"调试超能力"。对于只在某些时候出现的 Bug,目标不是干净的复现,而是更高的复现率:循环触发、并行化、加压、注入 sleep,直到 flake 率达到足够高、可以对着调试为止。原文档的量化经验是:50% 的 flake 可调试,1% 不可调试,所以要不断抬升复现率直到可调试。
当确实无法构建回路时,技能被指示停下并明说:列出尝试过什么,然后向用户索取——(a) 能复现该问题的环境访问权,(b) 脱敏后的捕获工件(HAR 文件、日志转储、core dump、带时间戳的屏幕录制),或 (c) 添加临时生产插桩的许可。它不应在没有任何回路的情况下继续提出假设。这一点在 SKILL.md 中被反复强调:"如果你发现自己正在读代码、在没有这个命令之前就构建理论——停下来:直接跳到假设正是这个技能要防止的失败。没有能变红的命令,就没有 Phase 2。"
补丁 1.2.3 新增:Redact 脱敏规则
技能要求 Agent 展示命令、输出和捕获工件,因此 CHANGELOG 记录的 1.2.3 版本在 SKILL.md 中加入了Redact章节,使其成为每次展示前的第一个动作:
- 对每个敏感内容写
<REDACTED>占位; - 构建回路时面向环境变量,让凭据留在环境里而不是出现在展示内容中;
- 捕获的工件携带 auth 头:只引用承载信号的那几行。
如果脱敏后的输出不足以诊断 Bug,技能应当明说并询问用户,而不是冒险泄露凭据。
三、阶段之间的门禁:门(gates),不是清单
六个阶段被设计成门禁而非勾选清单,每一扇门只有某个具体条件为真才打开:
| 门 | 必须为真的条件 |
|---|---|
| 进入 Phase 2 | 存在一个具名命令,已经运行过并把输出粘出来,能针对这个Bug 变红 |
| 进入 Phase 3 | 复现已达成且已最小化:剩下的每个元素都是承重的(load-bearing) |
| 进入 Phase 4 | 存在 3–5 条排序后的、可证伪的假设,每条都陈述其预测,并且在测试任何一条之前展示给你 |
| 进入 Phase 5 | 探针(probes)映射到具体预测,一次只改变一个变量,每条调试日志带[DEBUG-a4f2]风格标签以便一次 grep 清理 |
| 完成 | 原始复现不再复现,插桩已移除,且被证实正确的假设被写进提交信息 |
值得特别说明 Phase 5 的逃生舱(escape hatch):回归测试在修复之前写,但仅当存在正确的缝(correct seam)——即测试所锻炼的是 Bug 在调用点真正发生的模式。如果唯一可用的缝太浅(Bug 需要多个调用者,却只能写单调用者测试;或单元测试无法复现触发 Bug 的那条链),那里的回归测试只会带来虚假信心。这种情况下,技能被指示明说没有正确的缝,而不是写一个浅测试。这个"缺失"本身就是发现——正是它把事后复盘路由到improve-codebase-architecture。
四、六阶段实战分解
Phase 1:构建反馈回路——"这就是技能本身"
SKILL.md 的开场白毫不含糊:"This is the skill.Everything else is mechanical."(这就是技能本身,其余都是机械工作)。如果你拥有针对这个Bug 的紧致 pass/fail 信号,你就能找到原因;二等分、假设检验、插桩都在消费它。如果没有,盯代码盯多久都没用。因此:激进、有创造力、拒绝放弃,把不成比例的精力花在这里。
Phase 1 的完成标准:回路是紧致且能变红的——你能指出一个命令(脚本路径、测试调用、一条 curl),它已经至少运行过一次(展示调用与脱敏后的输出),并且满足四张勾选表:
- ☐能变红(red-capable):驱动真实的 Bug 代码路径并断言用户的确切症状,能在本 Bug 上变红、修复后变绿。不是"运行不报错",它必须能抓住这个具体的 Bug。
- ☐确定性(deterministic):每次运行结论一致(flaky Bug 则钉住高复现率)。
- ☐快(fast):秒级,不是分钟级。
- ☐可无人值守(agent-runnable):人可以只通过
hitl-loop.template.sh介入。
Phase 2:复现 + 最小化
运行回路,看着它变红。三张确认表:
- ☐ 回路产生的是用户描述的失败模式,而不是旁边恰好发生的一个不同失败。错的 Bug = 错的修复。
- ☐ 失败跨多次运行可复现(非确定性 Bug 则复现率足够高,能对着调试)。
- ☐ 已捕获确切的症状(错误消息、错误输出、慢时序),供后续阶段验证修复确实命中。
然后最小化:把复现缩小到仍会变红的最小场景。每次只切掉一个输入、调用者、配置、数据或步骤,每切一刀重跑回路,只保留对失败承重的部分。收益有二:最小化复现缩小了 Phase 3 的假设空间(剩下值得怀疑的部件更少),并在 Phase 5 变成干净的回归测试。完成标志:剩下的每个元素都是承重的——移除其中任何一个都会让回路变绿。
Phase 3:假设排序
在测试任何一条之前,先生成3–5 条排序假设。单一假设生成会锚定在第一个貌似合理的想法上。每条假设必须可证伪:陈述它所预测的后果。
格式:"如果 是原因,那么 <改变 Y> 会让 Bug 消失 / <改变 Z> 会让它更糟。"
如果陈述不出预测,这条假设就是"感觉"(vibe),丢弃或打磨它。在测试前把排序清单展示给用户——他们往往有领域知识可以瞬间重排("我们刚部署了 #3 相关的改动"),或知道已被排除的假设。这是廉价检查点、省时利器。但不要阻塞:用户不在场(AFK)就按自己的排序继续。
Phase 4:插桩
每条探针必须映射到 Phase 3 的某个具体预测,一次只改变一个变量。工具偏好:调试器 / REPL 检查(环境支持的话)> 定向日志 > 绝不"全量打日志再 grep"。给每条调试日志打唯一前缀标签(如[DEBUG-a4f2]),清理时一次 grep 就能删光:未标记的日志存活,已标记的日志消亡。
性能分支(perf branch):对性能回归,日志通常是错的工具。替代方案是:先建立基线测量(计时 harness、performance.now()、profiler、查询计划),再做二等分。先测量,后修复。
Phase 5:修复 + 回归测试
回归测试先于修复编写,但仅当存在正确的缝。正确的缝 = 测试锻炼的是 Bug 在调用点真实发生的模式。如果唯一可用的缝太浅,回归测试在那里只会带来虚假信心。如果没有正确的缝,这本身就是发现——记下来,代码库架构正在阻止这个 Bug 被锁定,为下一阶段标记此问题。
有正确缝时的操作序列:
- 把最小化复现变成该缝上的失败测试;
- 看它失败;
- 应用修复;
- 看它通过;
- 用原始(未最小化的)场景重跑 Phase 1 反馈回路。
Phase 6:清理
声明完成前的必做项:
- ☐ 原始复现不再复现(重跑 Phase 1 回路)
- ☐ 回归测试通过(或"无缝"的缺席已被记录)
- ☐ 所有
[DEBUG-...]插桩已移除(grep 前缀确认) - ☐ 一次性原型已删除(或移到明确标记的调试位置)
- ☐ 被证实正确的假设写进 commit / PR 信息,让下一位调试者学到东西
五、人机协作回路:hitl-loop.template.sh 源码级讲解
当回路必须由人类手动点击、登录、观察时,技能用仓库自带的 scripts/hitl-loop.template.sh 把人结构化地驱动起来,而不是让人类变成随机噪声源。该脚本约 30 行,只有两个辅助函数:
#!/usr/bin/env bash # Human-in-the-loop reproduction loop. # Copy this file, edit the steps below, and run it. # The agent runs the script; the user follows prompts in their terminal. # # Usage: # bash hitl-loop.template.sh # # Two helpers: # step "<instruction>" → show instruction, wait for Enter # capture VAR "<question>" → show question, read response into VAR # # At the end, captured values are printed as KEY=VALUE for the agent to parse. set -euo pipefail step() { printf '\n>>> %s\n' "$1" read -r -p " [Enter when done] " _ } capture() { local var="$1" question="$2" answer printf '\n>>> %s\n' "$question" read -r -p " > " answer printf -v "$var" '%s' "$answer" } # --- edit below --------------------------------------------------------- step "Open the app at http://localhost:3000 and sign in." capture ERRORED "Click the 'Export' button. Did it throw an error? (y/n)" capture ERROR_MSG "Paste the error message (or 'none'):" # --- edit above --------------------------------------------------------- printf '\n--- Captured ---\n' printf 'ERRORED=%s\n' "$ERRORED" printf 'ERROR_MSG=%s\n' "$ERROR_MSG"要点:
step显示一条操作指令并等待回车,用于"请登录""请点击某处"这类无需回传的动作;capture提问并把回答读入变量,用于回传关键观测(是否报错、错误文本等);- 脚本末尾以
KEY=VALUE格式打印捕获结果,Agent 直接解析这段可解析输出; - 1.2.3 补丁明确:
capture会把值回显到终端供 Agent 读取,所以观测交给 capture,登录之类的动作留在 step,避免把凭据流经 Agent 上下文; - 使用方式是复制脚本、按注释编辑
# --- edit below与# --- edit above之间的步骤,然后bash hitl-loop.template.sh运行。
脚本同样遵守脱敏原则:既然展示命令、输出和捕获工件是技能的要求,那么先脱敏一切秘密,把[DEBUG-a4f2]风格的干净观测留在线索里。如果脱敏后的输出不足以诊断,明说并询问用户。
六、常见问题:边界、误触发与已知缺口
它在我只想要直接答案的快速问题上触发了。这是该技能被反馈最多的真实问题,尤其在激活阈值较低的模型上:模型把一段问题描述误判成诊断请求,先煞有介事地构建复现(往往价值有限)再回答,造成明显延迟。仓库中记录到四人反馈了同形状的问题。官方认可的修正是"先轻后重"(问题确实需要时才升级到重流程),但该改动尚未落地——技能的校准是针对 Claude Code 的调用行为做的,低阈值模型会过度触发。在它演进之前,实用解是:明确说"直接回答即可,不要诊断",或在你的 harness 里为它关闭模型自动触发。
能让它对着一个代码库指出性能问题在哪吗?不能。它诊断的是你能具名的一个失败。它的性能分支针对的是有症状的回归(建立基线测量,然后二等分,先测量后修复),而非主动扫描。针对主动版本的技能曾被提议并关闭,目前仓库中没有对应技能。
它在写修复之前会停下来问我吗?不会。只有 Phase 3 有人工检查点:排序假设清单在任何一条被测试前展示给你,如果你不在场它就按自己的排序继续。插桩与修复之间没有门禁,因此 Agent 可能在你就根因达成一致前就开始写代码。该门禁在仓库中被请求,至今仍是 open issue。如果你想要,在调用技能时说明即可。
我已经对这份 Bug 报告跑过/triage了,这是重复劳动吗?部分重复,且两个技能都不承认这一点。Triage 的验证步骤本质上是 diagnosing-bugs Phase 1–2 的浅层受限版本,但两个文件互不提及对方。Triage 做有界的"这到底是不是 Bug、表面在哪"的检查;本技能做彻底版。先跑 triage 并非浪费(它的验证往往给你 Phase 1 的大部分原材料),但在这里你会被要求重做一遍规范流程,且不会有任何交叉引用提醒你。
它粘出的复现输出会泄露秘密吗?有可能。技能要求 Agent 粘贴调用及其输出,并要求 HAR 文件、日志转储、core dump 等工件,而没有任何指令做清洗。仓库中有 issue 专门提出此问题(凭据、token、cookie 与个人数据搭车进入聊天、issue 或 PR)并提议红色action(redaction)护栏,仍是 open 且未实现。把脱敏当成你自己的责任,尤其是在输出流向任何公开场合之前——这正是 1.2.3 在 SKILL.md 开头加入 Redact 章节的原因。
我的安全扫描器把该技能标为高风险。Snyk 会标记它,且这是误报。它是整套技能中唯一附带可执行 shell 脚本(hitl-loop.template.sh)、带"运行它"和"curl 一个 dev server"指令的技能——"附带 .sh + 运行指令 + 出站 HTTP"足以触发静态扫描器。脚本本身只是约 30 行read -r -p的等待人工输入的提示。扫描器评级的是能力表面(capability surface),而非已证实的利用。
/diagnose去哪了?v1.0.0 更名为/diagnosing-bugs,旧名已不存在(CHANGELOG 中记录为 breaking change)。任何链接/diagnose的东西(wrapper 技能、保存的 prompt)都需要更新。
七、它生效的判断标准(It's working if)
- 在提出任何理论之前,先向你展示一条命令及其红色输出。如果理论先出现,说明技能没有在运行。
- 它复现的失败正是你报告的失败,而不是它顺路发现的邻近失败。
- 它在猜测之前先缩小复现,并能说出每个保留片段为什么承重。
- 在任何一条被测试之前,你被展示 3–5 条排序假设,每条都带你能证伪的预测。
- 它添加的每条调试日志都带
[DEBUG-a4f2]标签,声明完成时对该标签的 grep 结果为空。 - commit / PR 信息点明了哪条假设是对的。
- 当它无法用测试锁定 Bug 时,它明说,而不是写一个浅测试。
八、它在技能体系中的位置
diagnosing-bugs是一个随时可取的独立技能(reach-for-it-anytime standalone):东西坏了就切入,修复和回归测试就位就切出;它不持有状态,也不需要任何前置设置。在 ask-matt 的路由中,"Something's broken"(有东西坏了)被路由到这里——它是主流程之外的一条 on-ramp,与/triage(外部报告堆积)并列。
两个邻居值得注意:
- improve-codebase-architecture:当真正的发现是"代码没有缝来锁定 Bug"时,接收本技能的交接(handoff);推荐在修复落地之后、信息更多时做出。
- triage:位于其上游,处理来自他人的原始 Bug 报告,做前两个阶段的更浅版本。二者文本互不提及对方,但功能上是互补关系——triage 验证"这是不是真的 Bug、表面在哪",diagnosing-bugs 追根因。对应文档参见 docs/engineering/triage.md。
技能本身与配套脚本、元数据的完整实现位于 skills/engineering/diagnosing-bugs/(含SKILL.md、scripts/hitl-loop.template.sh、agents/openai.yaml),工程类技能的完整清单与说明见 skills/engineering/README.md。整个仓库以 Claude Code 与 Codex 双 harness 为目标,agents/openai.yaml提供 Codex 侧的 UI 元数据,disable-model-invocation/policy.allow_implicit_invocation则控制各自 harness 的隐式触发开关。
【免费下载链接】skillsSkills for Real Engineers. Straight from my .agents directory.项目地址: https://gitcode.com/GitHub_Trending/skills13/skills
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考