关于 investigating-ci-failures
这项技能提供基于 CI 日志与作业历史调查测试失败的方法,帮助开发者判断故障属于自身 PR、主干变更还是偶发问题,并定位相关提交。它强调用现有数据完成归因,无需重新运行 CI。
主要功能
- 01按指纹归类 pytest 失败
- 02区分 PR 故障、主干故障与偶发失败
- 03通过作业历史定位提交边界
- 04核对修复提交与责任 PR
- 05说明数据延迟和覆盖范围限制
适用场景
- 01调查单个失败测试的原因
- 02确认主干何时变红及修复时间
- 03判断多个分支上的失败是否源于主干
- 04核实失败是否具有偶发特征
MCP 相关
使用 engineering-analytics-broken-tests 进行整体故障分类,并按需调用失败日志、偶发测试等 MCP 工具。调查还会查询 engineering_analytics_ci_failures 和 engineering_analytics_ci_job_history 两个数据仓库视图。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ posthog/ ai-plugin --skill investigating-ci-failures安装后在 Claude Code 中输入/investigating-ci-failures确认已加载。
附带文件references/investigation-queries.md
详细介绍
调查 CI 失败
任务:针对一个失败的测试或一次红灯运行,得出开发者可以据此采取行动的结论—— 你的代码导致 / 源自主干 / 不稳定。如果源自主干,还要找出问题提交 SHA、作者、PR,以及修复是否已经合入。以下所有推导都基于已有数据;无需重新运行 CI 即可得出结论。
两个数仓视图是分析基础(均为非物化视图——数据始终最新,可随时查询):
engineering_analytics_ci_failures— CI 日志中每行 pytestFAILED <nodeid>对应一行, 已预先生成指纹(fingerprint= 测试 ID + 数字/十六进制归一化后的错误)。按fingerprint分组,即可获取首次/最近出现时间、出现次数和分支分布。engineering_analytics_ci_job_history— 每次作业尝试对应一行,包含conclusion和提交 归属信息:head_sha、commit_author_name、commit_message、commit_pr_number(生成该提交的已合并 PR,这是 master 推送运行唯一的 PR 归属信息)。绿色运行记录在此;日志中只有失败记录,因此所有“何时变红 / 再次转绿”的问题都必须从这里查询,而不是从日志查询。
每个步骤的可直接复制 SQL 都在 references/investigation-queries.md 中。
先看全局:当前有哪些问题
如果想知道“现在有哪些 CI 失败值得关注”(还没有特定测试目标时),engineering-analytics-broken-tests MCP 工具会对所有仍在发生的失败一次性进行以下分类:按指纹汇总最近 2 天的失败,并为每项标注
breaking_master / blocking_merge_queue / novel_burst / potentially_resolved / flaky /
pr_only,按紧急程度排序,同时提供 breaking_master_jobs(默认分支最近一次运行结果为红灯的作业)。将它用作分诊入口,然后进入下方的单个失败调查流程,查明问题来源。它是手动生成指纹流程的自动化对应方案;要将特定失败定位到某个边界和作者,仍需使用手动查询。
blocking_merge_queue 是下方手动表格未涵盖的唯一类别,因为它看起来像单分支失败,实则不然。合并队列会在门禁分支(trunk-merge/pr-<n>/…)上运行完整测试套件;该分支包含 master、该 PR,以及队列中所有与其影响目标有重叠的前置 PR(实际上通常是队列中的大多数 PR)。因此,此处的失败发生在一个已通过该 PR 自身 CI 的提交上:原因可能是与期间合入或排队的改动冲突,也可能是队列中其他 PR 自身的问题,默认不应归咎于该 PR 的缺陷。应将它理解为“此问题阻止了合并”,列出门禁分支运行过的内容(查询 8),并与 trunk 对比,而不是只看该 PR。
四种故障形态
先为故障建立指纹(参考资料中的查询 1),然后读取其形态——看三列即可分类:
| 形态 | 解读 | 后续步骤 |
|---|---|---|
| 1 个分支,任意时间窗口 | 该 PR 自身的问题 | 查看其故障日志;完成 |
1 个 trunk-merge/pr-<n>/… 门禁分支 | 队列中的其他 PR,或问题已修复并入库 | 执行查询 8,然后比较门禁分支与 trunk |
| 多个分支,短时间内密集出现,命中 master | trunk 故障(master 当前或曾经标红) | 边界查询 → 找出根因(如下) |
| 多个分支,数天或数周内零星出现 | 间歇性故障 | 使用 engineering-analytics-flaky-tests 进行佐证 |
为何跨分支意味着 trunk 有问题:PR CI 会运行与 master 合并后的 PR,因此,master 上的一个坏提交会导致所有同时运行的 PR 失败。在短时间窗口内,多个互不相关的分支都出现故障,这是 master 合并故障的特征,而不是这些 PR 的代码有问题。如果提问者的 PR 没有责任,请明确告知——这通常是回答中最有价值的一句话。
trunk 故障 → 找出根因
运行边界查询(查询 2):查看仅包含 master 的故障作业历史,并按 created_at 排序。模式一目了然:
... success success | failure failure ... failure | success ...
^ first red = the culprit row ^ first green = the fix row
根因所在的记录包含全部信息:head_sha、commit_author_name、commit_message(其中说明了变更内容)和 commit_pr_number。首次通过的记录以相同方式标出修复。指明责任人之前,先检查可信度:根因提交是否有可能影响故障区域(根据其消息/PR 差异与故障测试所在模块进行判断)?如果边界落在无关提交上,说明可能是分片或时序噪声——扩大时间窗口,并检查相邻提交后再下结论。
然后验证 ci_failures 中的故障时间窗口是否吻合(first_seen 应在根因合并后不久,last_seen 应在修复后不久,随着 PR 队列排空而结束)。若不吻合,说明你看到的是两个恰好共用同一测试的问题。
间歇性故障 → 佐证,不要猜测
称其为“间歇性故障”之前,先查看同一分支的其他尝试。 单次运行无法区分间歇性故障与确定性故障,而 ci_job_history 已包含该分支每次尝试的 conclusion。如果某个分支上的每次运行都标红,就不存在可供查找的间歇性故障;建议重跑只会浪费一次运行,因为重试不会改变任何输入。
仅凭零星出现的形态只能作为线索,不能作为证据。engineering-analytics-flaky-tests MCP 工具会读取按测试划分的 CI 时间跨度数据(重跑后通过信号——某项测试在同一作业中失败后又在重试时通过),在有覆盖的情况下,这是更有力的信号。只看次数,不看比率:低于数据生成器时长阈值的通过运行不会被记录,因此没有可靠的分母。
每次回答都必须说明的注意事项
- 日志中只有失败记录。
ci_failures中没有绿色基线;缺少指纹只是弱证据(任务可能根本没有运行)。绿色记录仅来自ci_job_history。 - 指纹仅适用于 pytest(v1)。 Jest / playwright / cargo 的失败会出现在原始失败日志中,但不会记录在
ci_failures中。对于这些失败,请通过 MCP 工具engineering-analytics-ci-failure-logs(按 PR 查询)/engineering-analytics-run-failure-logs(按运行查询)查看原始失败日志。 获取某次运行时,请携带ci_engine和run_id,或携带latest_ci_engine和latest_run_id。 引擎为 null 表示历史来源未知;请勿按整数 ID 将其与已知引擎关联。 - 测试运行前就失败的任务,在所有测试级数据面中都不可见。 没有
FAILED行,就没有指纹、没有 span,也没有内容可供 flaky-tests 工具读取——失败确实发生了,但测试级查询应保持沉默,而不是说“没问题”。设置、docker 和 runner 失败只会显示为任务结论(查询 7);这也是此处唯一能得出可信失败率的数据面,因为它记录了成功记录。 - 归咎于缓存前,先检查
runs-on。 在depot-*runner 上运行的任务会通过 Depot Cache 解析actions/cache,而 GitHub Actions 缓存 API 无法查看 Depot Cache,因此此处结果为空并不代表缓存已被清除。请查看任务自身的Cache not found for input keys:行,确认实际请求的键。无论哪种情况,两个步骤都会显示为成功:缓存未命中时,actions/cache/restore仍会成功;actions/cache/save失败只会产生警告,因此成功的生产者任务可能什么也没存下来。 - 不同数据源的新鲜度各不相同。 日志近乎实时地流入;数仓中的 jobs/runs 表通过 webhook 同步,可能会延迟。处理正在发生的事故时,请从
ci_failures开始,并在信任边界前检查数仓的max(created_at)(查询 5)。基于过时数仓计算出的边界会指向错误的提交。 - 运行的
conclusion可能过时,直到workflow_runwebhook 将其更新为止(SPEC §7)——请谨慎看待非常近期且“无失败”的尾部记录。 - 重试:
run_attempt > 1的行表示同一任务重新运行。失败在第 2 次尝试时消失,是不稳定信号;持续失败到第 5 次或之后,则是确定性失败。 - 回退: 回退会显示为一个新的首个成功(或首个失败)提交,其
commit_pr_number是执行回退的 PR——归因跟随回退,而非原始提交。 - 所有日志查询都必须限定时间范围。 失败日志流规模很大;不设范围的扫描会触及读取上限。14 天的范围几乎足以覆盖所有调查。
- 也要查询对应的数仓数据。 若
ci_job_history查询仅按created_at确定时间窗口,就会强制扫描全部 jobs——解析后的时间戳是计算列,parquet 扫描无法据此剪枝。除了精确的created_at条件外,还要加上较窗口起始日早一天的粗略字符串下限created_at_raw >= '<YYYY-MM-DD>',以跳过无关数据;created_at仍是精确筛选条件。
选择使用场景
| 问题 | 使用 |
|---|---|
| “当前 CI 中哪些问题尚未解决?” | engineering-analytics-broken-tests MCP 工具(已分诊、分类) |
| 问题相同,但想从终端查看 | hogli ci:insights — 这些端点,范围限定为当前检出仓库 |
| “我的 PR 的 CI 为什么失败?” | engineering-analytics-ci-failure-logs MCP 工具(限定 PR、分组) |
| “是谁弄坏了 master / X 是什么时候开始的?” | 使用上述两个视图和工作流 |
| “X 是否不稳定?” | 根据 ci_failures 和 flaky-tests 工具梳理情况 |
| “这种设置/基础设施故障常见吗?” | 作业结论,查询 7(不会为此类问题生成测试行) |
| “master 当前有哪些失败?” | broken-tests 工具中的 breaking_master 行和 breaking_master_jobs |
| “CI 是否缓慢/成本高/PR 卡住了?” | diagnosing-ci-and-merge-bottlenecks 技能 |
| “将其保存为仪表板/洞察” | turning-engineering-analytics-into-insights 技能 |
输出预期
先用直白的话给出结论,并说明责任归属或排除嫌疑(“不是你的 PR——master 在 UTC 时间 08:01 到 09:58 期间因 #68727 而故障;由 #68855 修复”),然后给出证据:边界行、指纹时间窗口、出现次数/分支计数。客观指出作者(他们编写了问题提交),不要指责——提交说明和 PR 链接留给读者自行判断;而且很多时候,“问题提交”只是一次合理的更改,却依赖了未模拟的测试依赖项。