Learning AI Quality 返回 KuthorX Blog II博客首页

第 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 截了图但只说页面正常显示”
“一直重复点按钮,然后漏报”记的是下游症状,第一个错在更前面“订单取消:点了按钮但页面没变化,选择器指向了一个隐藏元素”

四条规则:

  1. 写看到的现象,先不追原因。FAQ 的建议是先别管为什么出错,等决定修哪些之后再查根因,这样笔记质量更高,也能多看一些数据。
  2. 刚开始只记第一个上游失败。登录态丢了,后面的"在登录页里反复点”、“判定没 Bug"都是连带的。FAQ 对长 trace 的建议也是这一条:“focus on the first upstream failure”,因为错误会层层叠加。FAQ 也说,熟练之后可以再标同一条 trace 里相互独立的失败;但下游的连带症状不算独立失败。
  3. 不是模型的问题也记。FAQ 专门有一问回答这件事:凡是让产品变差的都记,包括和模型无关的功能缺失。登录态丢失很可能是 harness 没处理会话过期,它同样让测试 Agent 漏 Bug。
  4. 通过的 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 可以帮忙出第一版分组,但要逐条审。