第 27 章
第 6 周:测试 Agent 为什么漏掉 Bug?从读 trace 到失败分类
错误分析:先读 trace,再定指标。按 Hamel Husain 和 Shreya Shankar 的流程走一遍:开放编码先只记第一个上游失败,轴向编码归成失败分类,再计数排序;Shankar et al. 2024 提出的 criteria drift 说明了分类为什么只能从数据里长出来;读多少条才够(饱和与 rule of three)。在 120 条合成 BugHunt trace 上实跑,再用本机 Codex 会话的只读聚合统计说明自动信号不等于失败。一段讲解视频,一个错误分析工作台。
BugHunt 的测试 Agent 在 8 个注入了 Bug 的功能区上跑,召回率 45%。常见的反应有三种:换一个更强的模型;在 prompt 里加一句"请仔细检查";接一个现成的"有用性 1~5 分"Judge 天天打分。三种做法都没回答一个问题:它到底是怎么漏的。是根本没进到页面,是看到了错误数字没认出来,还是把后端 503 当成了产品 Bug?这几种的修法完全不同。
错误分析就是先回答这个问题:一条条读 trace,记下第一个出错的地方,归成几类,数一数,再决定先修什么、给什么写评测。这是第 6 周的第 1 讲,本周后面讲二元 rubric、Judge 校准和 Judge 偏差,都要用到这里整理出的失败分类。trace 怎么记,见第 2 周「Agent 做错了,你怎么知道它错在哪一步?」。
讲解视频
互动演示
两个演示。第一个是错误分析工作台:120 条合成 BugHunt trace,一条条读,左边是这条 trace 的每一步和评审笔记,右边是到目前为止各类的条数和 Wilson 区间;可以切换"只记第一个上游失败"和"看到什么记什么",看排行怎么变;下面的曲线显示读到第 n 条时见过几种失败模式,读满 30 条时会整理出第一版分类,之后放不进去的条数会单独显示。第二个是"读多少条才够"的计算器。数据和本地脚本的输出一致,页面底部有自动判分的练习。
为什么先做错误分析,而不是先定指标
Hamel Husain 和 Shreya Shankar 的 FAQ 《AI Evals: Everything You Need to Know》 把这件事放在第一位:“Error analysis is the most important activity in evals.",理由是它"helps you decide what evals to write in the first place”。对现成的通用指标,他们的看法是:拿来当质量度量会浪费时间、制造虚假的信心,但可以用来挑出值得看的 trace。这份 FAQ 页面标注发布于 2026-09-18、修改于 2026-09-21(本文 2026-10-01 访问)。作者在开头说明,这些是他们认为多数情况下有效的"sharp opinions",不是普遍真理。
更根本的理由来自一篇论文: Who Validates the Validators? Aligning LLM-Assisted Evaluation of LLM Outputs with Human Preferences ,作者 Shreya Shankar、J.D. Zamfirescu-Pereira、Björn Hartmann、Aditya G. Parameswaran、Ian Arawjo,发表于 UIST 2024( DOI 10.1145/3654777.3676450 )。他们做了一个叫 EvalGen 的工具,帮用户写评估标准、生成断言(Python 函数或 LLM 评分 prompt),再用用户亲手打的分去挑和用户最一致的实现。9 位业界从业者试用时,作者观察到一个现象,起名 criteria drift。摘要原文:“users need criteria to grade outputs, but grading outputs helps users define criteria.",也就是要打分得先有标准,标准却是在打分的过程中才想清楚的。
论文第 7.3.1 节记录了两种漂移:
- 看到新"类型"的坏输出,参与者想加新标准;
- 打得越多,对已有标准的理解也在变。例子是一个"抽出来的实体都是专有名词"的标准,两位参与者一开始只要有一个不是专有名词就判差,看了更多输出后想改成"大部分是专有名词”。
作者据此认为,评估标准不可能在人看过 LLM 输出之前就完全定下来。对我们来说结论很直接:先读 trace,让失败分类从数据里长出来,再给每一类决定怎么修、要不要写评测。
四步:读 trace → 开放编码 → 轴向编码 → 计数排序
FAQ 里的流程,对应到 BugHunt:
| 步骤 | 做什么 | FAQ 里的建议 |
|---|---|---|
| 1. 收集 trace | 攒一批有代表性的 trace | 没有真实数据,就先用合成数据起步;先随机抽样,再用异常值、按指标排序、分层抽样去找有意思的 trace |
| 2. 开放编码(open coding) | 一条条读,用自由文本写下哪里不对,FAQ 说它像写日记 | 刚开始只记 trace 里第一个失败,因为上游的错会引出下游的错;至少先自己标 30 条,再看 agent 的建议 |
| 3. 轴向编码(axial coding) | 把笔记归成一张失败分类表(failure taxonomy) | “Axial coding is the most important step.";做完数每一类的条数;可以让 LLM 先帮忙分组,但要自己审 |
| 4. 迭代 | 继续读,直到新的 trace 不再带来新类别、也不再改变已有类别 | 这叫理论饱和(theoretical saturation);建议至少读 100 条,还在学到新东西就继续读 |
开放编码和轴向编码这两个词来自质性研究。谁来做:FAQ 认为,对多数中小公司,指定一位懂业务的领域专家说了算最有效,他们叫这个人 “benevolent dictator”;把错误分析外包给第三方通常是个大错误(他们也列了例外情况)。BugHunt 里,这个人就是最懂被测应用的测开。
Hamel 2025-03-24 的 A Field Guide to Rapidly Improving AI Products 里有一个 NurtureBoss 的案例:先在表格里每行一段对话,写开放笔记;再用 LLM 整理出失败分类;最后给每行打上分类标签计数,结果三类问题占了全部问题的六成以上。
开放编码:一条笔记该怎么写
| 不好的笔记 | 问题 | 好的笔记(合成样例) |
|---|---|---|
| “漏报了” | 这是结局,不是哪里出了错 | “跳到优惠券叠加后又被重定向回登录页,后面都在登录页里转” |
| “模型能力不够” | 是猜原因,看不出发生了什么 | “购物车合计页面上的数字明显不对,Agent 截了图但只说页面正常显示” |
| “一直重复点按钮,然后漏报” | 记的是下游症状,第一个错在更前面 | “订单取消:点了按钮但页面没变化,选择器指向了一个隐藏元素” |
四条规则:
- 写看到的现象,先不追原因。FAQ 的建议是先别管为什么出错,等决定修哪些之后再查根因,这样笔记质量更高,也能多看一些数据。
- 刚开始只记第一个上游失败。登录态丢了,后面的"在登录页里反复点”、“判定没 Bug"都是连带的。FAQ 对长 trace 的建议也是这一条:“focus on the first upstream failure”,因为错误会层层叠加。FAQ 也说,熟练之后可以再标同一条 trace 里相互独立的失败;但下游的连带症状不算独立失败。
- 不是模型的问题也记。FAQ 专门有一问回答这件事:凡是让产品变差的都记,包括和模型无关的功能缺失。登录态丢失很可能是 harness 没处理会话过期,它同样让测试 Agent 漏 Bug。
- 通过的 trace 也要读。没 Bug 的功能区上说"没 Bug”,可能是根本没进到页面,结论碰巧对。
轴向编码:分类表要满足什么
- 按原因分,不按结局分。“漏报 / 误报"是另一个维度。把它放进分类表,它会因为每条失败都带一个而排到第一,却什么也没告诉你。
- 互斥:每条失败恰好归一类。放不进去就新开一类,不要硬塞进"其他”。
- 能判定:拿一条新 trace,另一个人按类名和说明也能归到同一类。两个人归得一不一致怎么量,本周讲 Judge 校准时再说。
- 会变:读得越多,类会拆、会并、会重新定义,这就是 criteria drift。
计数的单位是 trace。每条失败 trace 只算一次(第一个上游失败),各类条数加起来正好等于失败条数,占比才有意义。熟练后补标的、相互独立的第二个失败,要和第一个上游失败分开统计。下面实验里的"看到什么记什么"把下游症状和结局也记了进去,FAQ 并不推荐这样做,这里只是用它当反例,看排行会怎样失真。
动手实验:120 条合成 BugHunt trace
代码在本地 week06_Judge与错误分析/code/error_analysis/,只用标准库,不调用任何模型 API。数据是合成的:
- 12 个功能区,其中 8 个注入了 Bug,每个功能区跑 10 次,共 120 条 trace(种子 20261006)。
- 每条 trace 分 7 个阶段:规划 → 导航 → 操作 → 观察 → 判定 → 报告 → 提交。
- 9 种失败模式按固定条数分配到 trace 上,失败那一步之后是连带的下游步骤;评审笔记从每种模式的几种说法里随机挑一种。
因为失败模式是我事先写进生成器的,这个实验不能演示"发现"真实的失败类型,只用来演示计数、排序、区间和饱和这几步,以及其中的坑。
$ python3 traces.py && python3 analysis.py
已写出 traces.json:120 条 trace,失败 68 条,通过 52 条(合成数据,种子 20261006)
合成数据:120 条 trace,失败 68 条(56.7%),通过 52 条
[只看结局]
有 Bug 的 trace 80 条,召回率 45.0%
漏报 39
正确报告 36
正确:无 Bug 16
误报 14
结论碰巧对 10
报告不可复现 4
重复报告 1
没 Bug 的 40 条里说'没 Bug'的 26 条,其中 10 条根本没测到页面(结论碰巧对)
[第一个上游失败] 条数 占失败 占全部 全部的 Wilson 95%
看到了没比对预期 17 25.0% 14.2% [9.0%, 21.5%] 累计 25.0%
登录态丢失没进页面 14 20.6% 11.7% [7.1%, 18.6%] 累计 45.6%
操作没生效 11 16.2% 9.2% [5.2%, 15.7%] 累计 61.8%
环境故障报成 Bug 8 11.8% 6.7% [3.4%, 12.6%] 累计 73.5%
误解业务规则 6 8.8% 5.0% [2.3%, 10.5%] 累计 82.4%
只测了正常路径 5 7.4% 4.2% [1.8%, 9.4%] 累计 89.7%
报告缺复现步骤 4 5.9% 3.3% [1.3%, 8.3%] 累计 95.6%
入口藏在折叠菜单里 2 2.9% 1.7% [0.5%, 5.9%] 累计 98.5%
超时重试导致重复提交 1 1.5% 0.8% [0.1%, 4.6%] 累计 100.0%
前三类合计 42 / 68 = 61.8%
第 1 类 vs 第 2 类:17 vs 14,条件二项检验双侧 p = 0.720
[看到什么记什么] 标签 条数
漏报 39
重复调用同一工具 19
看到了没比对预期 17
登录态丢失没进页面 14
误报 14
操作没生效 11
环境故障报成 Bug 8
报告描述含糊 6
误解业务规则 6
只测了正常路径 5
报告缺复现步骤 4
入口藏在折叠菜单里 2
超时重试导致重复提交 1
[第一个失败发生在哪个阶段]
规划 5
导航 16
操作 11
观察 17
判定 14
报告 4
提交 1
[阅读顺序:种子 6] 读前 30 条见到 6 种失败模式;见齐 9 种在第 74 条
第一版分类套到全部 68 条失败上:7 条放不进去,缺的类:入口藏在折叠菜单里, 报告缺复现步骤, 超时重试导致重复提交
读到第 n 条时见过的种数:10→5,20→6,30→6,50→8,80→9,100→9,120→9
...
(后面关于饱和的输出放到下一节。)逐段看。
只看结局,会把 10 条没测的当成通过
看板上能直接算的是结局:召回率 45.0%(80 条里报对 36 条),误报 14 条。没 Bug 的 40 条里,说"没 Bug"的有 26 条,按结局看都对了。但其中 10 条根本没测到:6 条没进到目标页面(登录态丢了 5 条、入口没找到 1 条),4 条进了页面但操作没生效,只是结论碰巧对。只看结局指标,这 10 条会被算成通过;读了 trace 才知道它们和漏报的那些是同一类问题。
只记第一个上游失败,各类加起来等于失败条数
按第一个上游失败计数,前三类(看到了没比对预期、登录态丢失、操作没生效)合计 42 条,占失败的 61.8%。
条数接近的两类,排名不一定可信。第一名 17 条、第二名 14 条,能说前者更常见吗?两类互斥,在落到这两类的 31 条里,如果两类一样常见,第一类的条数服从 Bin(31, 0.5)。17 对 14 的双侧精确 p 值是 0.720,这批数据分不出谁更常见。这和第 1 周「新 prompt 从 82% 涨到 86%,是真的变好了吗?」里的 McNemar 检验是同一个思路:只看两边各自多出来的那部分。
上面的 Wilson 区间(第 1 周「30 次挂了 2 次,失败率在什么范围?」)假设 120 条 trace 相互独立。这里每个功能区重复跑了 10 次,同一功能区的 trace 会扎堆(第 1 周「跑了 300 次,就是 300 个样本吗?」),真实的区间更宽。所以排序只是第一步,优先级还要看严重程度和修复成本,见后面"从分类到行动"一节。
看到什么记什么:结局和下游症状挤到了前面
如果每条 trace 把看到的全记上(根因、下游症状、结局各一笔),排行就变了:第一名"漏报"39 条,第二名"重复调用同一工具"19 条,都排在真正的根因前面。
- “漏报"是结局,每条漏报的 trace 都带一个,排第一不提供任何信息。
- “重复调用同一工具"在这批数据里只跟在"登录态丢失"和"操作没生效"后面(生成器里分别以 60% 和 50% 的概率出现)。看排行会以为该加一道重复调用检测,加上它只能让 Agent 停得早一点,Bug 照样漏。
第一个失败发生在哪个阶段
按阶段数:导航 16、观察 17、判定 14、操作 11,规划 5,报告 4,提交 1。BugHunt 是一条直线流程,按阶段计数就够了。FAQ 对带工具、多 agent 的流程推荐用转移失败矩阵(transition failure matrix):行是最后一个成功的状态,列是第一个失败出现的状态,一眼看出失败集中在哪一步转移上。
第一版分类,读下去就不够用了
按一个随机的阅读顺序(种子 6)读:前 30 条见到 6 种失败模式,据此整理出第一版分类。把它套到全部 68 条失败上,有 7 条放不进去,要新开 3 类(入口藏在折叠菜单里、报告缺复现步骤、超时重试导致重复提交);读到第 74 条才见齐 9 种。这对应论文说的第一种漂移:看到新类型的坏输出,就要加新类。合成数据里没有第二种漂移(已有类别被重新定义),真实数据里两种都会有。所以第一版分类不能当终版用。
读多少条才够:饱和与 rule of three
先看这批数据。不放回地随机读 n 条,某类(共 c 条)一条都没碰到的概率是 \(\binom{120-c}{n}\big/\binom{120}{n}\),期望见到的种数是各类"至少碰到一次"的概率之和:
[饱和:期望见过几种(不放回,精确)]
读 10 条:3.98 / 9
读 20 条:5.83 / 9
读 30 条:6.83 / 9
读 50 条:7.85 / 9
读 80 条:8.54 / 9
读 100 条:8.81 / 9
见齐 9 种所需条数(精确,容斥):中位数 74(P(T≤73) = 0.4967,P(T≤74) = 0.5092),95 分位 115
对照:随机顺序 5000 次模拟 中位数 73,95 分位 115,最少 12,最多 120
最少的一类(超时重试导致重复提交,1 条):读 30 条碰到它的概率 25.0%,读 100 条 83.3%
见齐 9 种,中位数约 74 条(精确计算;5000 次模拟得 73)。拖后腿的总是最少见的那几类:只有 1 条的"重复提交”,读 30 条只有 25% 的概率碰到。
线上的 trace 是一个很大的流,可以近似成放回抽样。某类失败占比 \(p\),各条独立,读 \(n\) 条:
$$ P(\text{至少碰到一次}) = 1-(1-p)^n,\qquad n_{95} = \left\lceil \frac{\ln 0.05}{\ln(1-p)} \right\rceil $$[线上流量(近似放回抽样)]
占比 10.0%:读 100 条至少见到一次 >99.9%;要 95% 把握需读 29 条
占比 5.0%:读 100 条至少见到一次 99.4%;要 95% 把握需读 59 条
占比 3.0%:读 100 条至少见到一次 95.2%;要 95% 把握需读 99 条
占比 2.0%:读 100 条至少见到一次 86.7%;要 95% 把握需读 149 条
占比 1.0%:读 100 条至少见到一次 63.4%;要 95% 把握需读 299 条
读 100 条一次没见到:95% 上界 精确 2.95%,rule of three 近似 3.00%
反过来读:100 条里一次也没见到某类,按第 1 周「30 次全过,能说明什么?」的反证法,它的占比 95% 上界是 \(1-0.05^{1/100}\approx 2.95\%\),rule of three 给 3/100 = 3%。所以 FAQ 建议的"至少 100 条"可以这样理解:占比 3% 以上的失败类型,大概率至少露一次面;更少见的可能一次都碰不到。见到一次也不等于能认出这是一类,通常要见过几次才敢单独立类。
两个前提要记住:一是各条 trace 独立,同一功能区、同一用户的 trace 会扎堆,有效样本更少,要多读或者分层抽;二是"读到饱和"是读到新 trace 不再带来新类别为止,上面的数字只是帮你估一个起点。
自动信号不等于失败:本机 Codex 会话的只读统计
能不能跳过人读,直接用程序算出的信号当失败率?拿本机 ~/.codex/sessions 试一下(rollout 格式见第 3 周「上下文快满了,Codex 怎么办?」)。本机的会话由多个 Codex 版本写成,命令执行有两种记录方式,要分开解析:
- A. 直接调用
exec_command(本机 2026-03~07 的会话)。工具输出头里有一行Process exited with code N,进程还在跑、返回 session ID 时没有这一行(core/src/tools/context.rs:534-540)。 - B. 代码模式(本机 2026-07 以后的会话)。模型调用的是一个叫
exec的自定义工具,在 JS 里再调tools.exec_command,退出码以结构化的exit_code字段返回给 JS(context.rs:448-479),rollout 里不会出现上面那行文字。好在每条命令结束时,Codex 还会写一个item_completed事件,里面是CommandExecution条目,带exit_code和status(core/src/tools/events.rs:581-612)。
ToolEventStage::Success { output, .. }
| ToolEventStage::Failure(ToolEventFailure::Output(output)) => {
let exec_result = ExecCommandResult {
// ...
exit_code: output.exit_code,
// ...
status: if output.exit_code == 0 {
ExecCommandStatus::Completed
} else {
ExecCommandStatus::Failed
},
};
(
events.rs:532-548
)注意 Codex 自己就把"退出码非 0"记成了 Failed:grep 没搜到,在它的记录里也是一次失败。工具调用本身出错或被拒时,退出码记成 -1,被拒的状态是 Declined、出错的是 Failed(同文件
:552-577
)。
codex_signals.py 只读扫描这两种记录,按命令里第一个程序名归到四个大类,只输出聚合数字,不打印任何命令、输出或对话内容,也不写任何文件。代码模式的会话里如果没有 CommandExecution 条目,退出码只出现在 JS 自己打印的文本里,没法可靠解析,只计数、不纳入统计。会话一直在增长,所以固定一个截止时间(2026-10-01 18:00,北京时间):
$ python3 codex_signals.py ~/.codex/sessions --until 2026-10-01T10:00:00Z
统计日期 2026-10-01,截止 2026-10-01T10:00:00Z(只读,只输出聚合数字)
rollout 文件 297 个
== A. 直接调用 exec_command:45 个文件(2026-03 ~ 2026-07)
带退出码 4,685 次,非零 178 次,占 3.8%
...
调用 4,861 次,其中输出里没有退出码 176 次:
进程还在跑(返回 session ID) 146
apply_patch 被拦截执行(Success. Updated …) 24
执行失败(exec_command failed …) 3
apply_patch 校验失败 3
== B. CommandExecution 条目(代码模式):176 个文件(2026-09 ~ 2026-10)
带退出码 10,591 次,非零 860 次,占 8.1%
...
status:completed→9731,failed→860
== 未纳入:代码模式但没有 CommandExecution 条目的文件 41 个(2026-07 ~ 2026-09),exec 调用 1,513 次;退出码只在 JS 打印的文本里,不做解析
== A + B 合计:221 个文件(2026-03 ~ 2026-10)
带退出码 15,276 次,非零 1038 次,占 6.8%
非零退出码分布:1→823,2→58,128→33,130→28,127→21,255→17,-1→11,3→9,4→5,5→5,56→5,143→5,10→4,137→3,113→2,125→2,129→2,7→1,22→1,23→1,35→1,52→1
[按程序大类] 带退出码 非零 非零占比 退出码 1 127 128
查看 / 搜索 7,332 252 3.4% 219 0 2
版本控制 1,527 84 5.5% 50 0 27
运行 / 构建 / 测试 1,443 258 17.9% 208 5 0
其他 4,974 444 8.9% 346 16 4
(... 处省略了 A、B 各自的分布和分类表,完整输出在本地运行即可看到。)
同一个数字,含义因程序而异。下面几条是本机实测:
- BSD grep 2.6.0 没匹配到返回 1。查看 / 搜索类的 252 次非零里 219 次是 1,多半只是没搜到、条件不成立(
test)或有差异(diff)。 - git 2.50.1 在非仓库目录执行
git status返回 128;git status、git diff这类命令参数写错返回 129(git log参数写错是 128)。退出码 128 的 33 次里有 27 次来自版本控制类。 - bash 3.2 找不到命令返回 127;进程被 SIGINT 中断返回 130(128 + 2),被
kill -9返回 137(128 + 9)。
反过来,失败也不一定表现为非零退出码。A 里有 176 次输出没有退出码,其中 146 次只是进程还在跑,24 次是 apply_patch 被拦截执行成功;但剩下 6 次是真的失败:3 次 exec_command failed(命令没能执行),3 次 apply_patch 校验失败。只数非零退出码,这 6 次一次也数不到。再加上 Agent 跑错了测试文件、测试全过的情况,退出码照样是 0。
所以 6.8%(或 A 的 3.8%、B 的 8.1%)都不是失败率,Codex 自己记的 failed 也不是。“其他"类里大多是本机自定义工具和 shell 控制结构,按第一个程序名分类本身也是粗略的启发式。自动信号的正确用法,正是 FAQ 说的那样:按指标排序、找异常值,决定先读哪些,读完再归类计数。比如先读非零退出码多的会话、没有退出码又不是"还在跑"的调用、重试多的会话、耗时异常的会话。
从分类到行动
分类排好序之后,每一类要决定的是"怎么修"和"要不要写评测”。FAQ 的几条建议:先修显而易见的问题,自动化评测只给修完 prompt 之后还存在的失败写;有客观规则能判的写代码检查;需要人判断的才上 LLM Judge,而且每种失败模式要标 100~200 条样例去校准它。对应到 BugHunt:
| 失败类别 | 性质 | 先怎么修 | 评测怎么写 |
|---|---|---|---|
| 看到了没比对预期 | Agent 没有判定依据 | 把需求里的预期值交给 Agent | 需要判断"报告是否和预期值比对过”,适合二元 Judge(下一讲) |
| 登录态丢失没进页面 | 很可能是 harness 问题 | 会话过期自动重新登录 | 代码检查:观察阶段的 URL 是不是目标页面 |
| 操作没生效 | 工具层问题 | 操作后等待并断言 DOM 有变化 | 代码检查:工具调用前后页面状态有没有变 |
| 环境故障报成 Bug | 判定问题 | 提交前查网络日志里的 5xx 和超时 | 代码检查 + 故障注入(第 5 周「环境坏了,测试 Agent 会把它报成 Bug 吗?」) |
| 误解业务规则 | 缺上下文 | 把业务规则放进上下文 | 需要判断,适合 Judge |
| 只测了正常路径 | 规划问题 | 测试点模板里强制列边界和异常 | 代码检查测试计划的覆盖项,或 Judge |
| 报告缺复现步骤 | 输出格式 | 报告模板 | 二元 rubric:有没有前置条件、步骤、期望、实际 |
| 入口藏在折叠菜单里 | 探索策略 | 探索时展开所有菜单 | 代码检查:访问过的页面集合 |
| 超时重试导致重复提交 | 工程问题 | 幂等键(第 5 周「超时了,再发一次安全吗?」) | 代码检查:同一 Bug 的报告数 |
可以看到,9 类里大半能用代码检查,真正需要 Judge 的只有几类。这正是先做错误分析的价值:Judge 只写给需要人判断的那几类,而不是一个笼统的"质量分"。下一讲「Bug 报告写得好不好,打 1~5 分还是判 pass / fail?」把这些类别写成二元 rubric。
常见错误说法
- “先把评分标准定好,再去看数据”:标准是看数据的过程中才想清楚的(criteria drift),先读 trace,再从笔记里整理 rubric,并定期回头更新。
- “漏报最多,先修漏报”:漏报是结局,不是原因,要按第一个上游失败归类。
- “每个症状都记一笔,越全越好”:下游症状会挤到根因前面,排行失真。熟练后可以补标相互独立的失败,但下游连带症状不算,并且要和第一个上游失败分开统计。
- “17 条比 14 条多,所以第一类更常见”:在这批数据里双侧 p = 0.72,分不出来;优先级还要看严重程度和修复成本。
- “结论对了,这条 trace 就算通过”:没进到页面、结论碰巧对的 trace,和漏报是同一类问题。
- “非零退出码 6.8%,命令失败率就是 6.8%”:退出码的含义因程序而异,grep 没匹配到也是 1;反过来,命令没能执行、apply_patch 校验失败时根本没有退出码,退出码 0 也不代表做对了。
- “读了 30 条没见过,就说明没有这类问题”:只能说它的占比 95% 上界约 10%(rule of three)。
- “让 LLM 把 trace 全分好类就行”:FAQ 的建议是开放编码自己做,至少先标 30 条;LLM 可以帮忙出第一版分组,但要逐条审。