首页
/ Next.js CI 分诊工作流:从 pr-status.js 报告到 Review Thread 闭环的处理手册

Next.js CI 分诊工作流:从 pr-status.js 报告到 Review Thread 闭环的处理手册

2026-09-05 18:45:51作者:凌朦慧Richard

本篇基于 Next.js 仓库中 pr-status-triage 技能的 workflow.md 展开,讲清楚一个 PR 在 CI 出现构建、Lint、类型或测试失败时的标准分诊路径:按什么优先级排障、如何判定“真失败”而非 flaky、每类失败对应哪些本地修复命令,以及如何在处理完 Review 意见后用 scripts/pr-status.js 完成“回复—解决”线程的闭环。读完后你可以直接套用这套流程处理本仓库中任意一个失败 PR,并理解 scripts/pr-status.js 生成的报告文件是如何支撑这一流程的。

技能定位:workflow.md 在 pr-status-triage 中的角色

Next.js 仓库通过 .agents/skills 目录维护一组按需加载的 Agent 技能文档。pr-status-triage 技能由三个文件组成:

  • SKILL.md — 入口,给出完整 7 步工作流与快速命令;
  • workflow.md — 本文主体,定义优先级顺序、失败判定规则、常见失败模式与线程解决规程;
  • local-repro.md — 本地复现指南,覆盖 dev/start 模式与 CI 环境变量对齐。

workflow.md 的适用场景是:当 CI 报告出现失败 job 或 PR 上出现未解决的 Review 线程时,按既定规程逐一处理。它的上游输入是 node scripts/pr-status.js 生成的报告目录 scripts/pr-status/results/,其中 index.md 是总入口,job-{id}.md 是单个失败 job 的详情,thread-N.md 是单条 Review 线程的详情。

优先级顺序:先阻断项,后评论

workflow.md 定义了严格的全局优先级:

  1. Build failures(构建失败)
  2. Lint failures(Lint 失败)
  3. Type failures(类型检查失败)
  4. Test failures(测试失败)
  5. Review comments(Review 评论,且在 CI 阻断项之后)

这条顺序的原则是“blocker-first”:越靠前的失败越会阻断后续所有环节(构建不过则无产物可跑测试),因此必须先解决。SKILL.md 中的快速命令支持从当前分支或指定 PR 号拉取状态:

node scripts/pr-status.js                  # 当前分支的 PR
node scripts/pr-status.js <number>         # 指定 PR
node scripts/pr-status.js [PR] --wait      # 后台模式,等待 CI 完成
node scripts/pr-status.js --skip-flaky-check  # 跳过 flaky 测试检测

从源码看,scripts/pr-status.jsrunAnalysis 流程会先清理 scripts/pr-status/ 输出目录,再依次拉取分支信息、最新的 build-and-test workflow run、失败 job 元数据、job 日志与 PR 评论数据(见 main 与 runAnalysis)。--wait 模式在 CI 仍在跑时会执行 gh run watch <run-id> --compact 等待结束后重新分析(L1728-L1741),这正对应 SKILL.md 中“后台运行(超时 1 分钟)然后读 index.md”的工作流第 1 步。

失败处理规则:默认“有罪推定”

workflow.md 的三条判定规则是分诊行为的核心约束:

  • 把每个失败 job 当作“由当前改动引起”来调查(Investigate each failing job as if it is caused by the current changes);
  • 不要默认假设它是 flaky(Do not assume flakiness by default);
  • 如果 job 输出里存在 "Known Flaky Tests" 一节,只把它作为历史上下文,而不是自动免责的理由

这三条规则在 scripts/pr-status.js 中有明确的实现对应:

  1. "Known Flaky Tests" 一节的生成逻辑generateIndexMd 会在 index.md 中输出标题为 ### Known Flaky Tests (failing on 2+ branches) 的小节,并明确注释“These tests also failed in recent CI runs across multiple different branches and are likely pre-existing flakes, not caused by this PR”(L851-L862)。注意措辞是 likely——报告本身也只给概率性提示,最终判定权仍在调查者手里,这与“不作为自动免责理由”的规则一致。

  2. flaky 判定算法getFlakyTests 会抓取最近 5 次其他分支的失败 run,并行拉取其中失败 job 的日志,统计“在 2 个及以上不同分支上都失败过”的测试路径,只有满足该条件才进入 flaky 集合(L1270-L1391)。其中还有两个保护性细节:单次 run 失败 job 超过 20 个时视为系统性故障而非 flaky(L1315-L1316);当前 PR 所在分支被排除以免自我匹配(L1297)。

  3. 失败结论的认定范围:脚本把 failuretimed_outstartup_failure 三种结论都计入失败(FAILED_CONCLUSIONSL266),即超时与启动失败同样进入 blocker 队列,不存在被静默放过的情况。

常见失败模式与修复命令

workflow.md 针对三类高频失败给出了具体命令。以下逐条结合仓库实际说明。

rust check / build 失败

适用场景:Turbopack 等 Rust 侧代码在 CI 的 rust check/build job 中失败。

cargo fmt -- --check   # 检查格式
cargo fmt              # 修复格式

从源码结构看,Rust 侧的 CI job 定义在 .github/workflows/build_and_test.yml 中,例如 rust-check job 通过 afterBuild: pnpm dlx turbo run rust-check 触发(L340),另有 test-cargo-unit 对应单测(L305)。格式问题是最常见的 rust check 失败原因,本地跑 cargo fmt -- --check 即可快速定位;若检查通过而 CI 仍失败,则应按“有罪推定”原则继续读 job-{id}.md 中的报错段落。

lint / build 失败

适用场景:JS/TS 侧的 lint、Prettier 或构建 job 失败。

pnpm prettier --write <file>   # 修复指定文件的格式
# 如需进一步修复,运行仓库的 lint 命令

package.json 中的 lint 脚本是聚合入口,实际包含 TypeScript 类型检查、Prettier 检查、ESLint、AST 扫描等多个子任务(L74);lint-fix 则串联了 Prettier 与 ESLint 的修复(L75)。因此 CI 上名为 lint 的失败,可能真正来自其中任意一个子任务,定位时建议先看 index.md 失败 job 表格中该 job 的链接,再进入 job-{id}.md 的失败段落。

test failures 失败

workflow.md 给出两条规则:

  1. 本地运行与 CI 完全一致的失败测试文件
  2. dev 与 start 模式必须与 CI job 对齐

本仓库的测试入口由 scripts/run-jest.sh 封装,package.json 中四种组合分别为:

"test-dev-webpack":   "scripts/run-jest.sh --mode=dev --bundler=webpack --headless --",
"test-dev-turbo":     "scripts/run-jest.sh --mode=dev --bundler=turbo --headless --",
"test-start-webpack": "scripts/run-jest.sh --mode=start --bundler=webpack --headless --",
"test-start-turbo":   "scripts/run-jest.sh --mode=start --bundler=turbo --headless --"

L26-L39)“dev 模式”指直接对开发服务器做断言,“start 模式”指先 next buildnext start 后对产物做断言,两者在模块解析、缓存行为上可能产生差异,模式不匹配时的本地通过/失败结论都不可信。

进一步地,CI job 往往还叠加了特性开关环境变量。scripts/pr-status.jsgetJobEnvVarsFromWorkflow 会解析 .github/workflows/build_and_test.yml 中每个 job 的 afterBuild 块,提取其中的 export VAR=value 语句,并按 job 显示名前缀匹配后写入 index.md### Job Environment Variables 小节(L154-L209L829-L849)。本地复现时必须镜像这些变量,local-repro.md 给出的示例是:

IS_WEBPACK_TEST=1 __NEXT_USE_NODE_STREAMS=true __NEXT_CACHE_COMPONENTS=true NEXT_TEST_MODE=start

其中 IS_WEBPACK_TEST=1 强制 webpack 模式(本地默认是 Turbopack),而 NEXT_SKIP_ISOLATE=1 会跳过包隔离——验证模块解析或编译期修复时绝不应带这个变量。

报告结构:从哪里找到“失败的那个测试文件”

“本地运行确切的失败测试文件”这一规则依赖报告提供的定位信息。scripts/pr-status.js 对每个失败 job 的日志做三层解析:

  • 结构化测试 JSON:从日志中的 --test output start-- {...} --test output end-- 块提取 Jest 结果(extractTestOutputJsonL541-L558),生成每个 job 的 job-{id}.md,内含 Test Results 统计与 Failed Tests 表格(Test File / Test Name / Error 三列,L1067-L1096);
  • 逐测试文件详情mergeRawTestOutputs 会把结构化结果与 ##[group]❌ test/... 原始日志块按测试路径合并,为每个失败测试生成 job-{id}-test-<path>.md,包含多次尝试(含重试内容)与失败断言全文(L642-L663L1111-L1160);
  • 日志分段extractSections 按 GitHub Actions 的 ##[group] 边界切分日志并标记含 ##[error] 的段落,落盘到 scripts/pr-status/intermediate/,供按段排查构建类失败(L665-L730)。

因此标准动线是:index.md 的 Failed Jobs 表格确定 job → job-{id}.md 的 Failed Tests 表格确定测试文件与断言名 → 用 pnpm test-dev-turbo test/path/to/test.ts(或对应模式)本地复现。

解决 Review 线程:先回复,后解决

workflow.md 的最后一节规定了 Review 线程的处理规程:当完成了评论要求的代码改动,或确认当前代码已满足该评论时:

  1. 先回复线程,说明所采取的动作
node scripts/pr-status.js reply-thread <threadNodeId> "Done -- <description of changes>"
  1. 再解决(resolve)线程
node scripts/pr-status.js resolve-thread <threadNodeId>

也可以一步完成回复并解决:

node scripts/pr-status.js reply-and-resolve-thread <threadNodeId> "Done -- <description of changes>"

workflow.md 特别强调:解决之前必须先回复动作描述,让 Reviewer 知道改了什么。

这里的 <threadNodeId> 无需手动查找:每次运行 pr-status.js 时,generateThreadMd 会为每个线程生成 scripts/pr-status/results/thread-N.md,文件底部的 ## Commands 一节直接写入了填好真实 thread.id 的三条现成命令(L1231-L1255)——这正是 workflow.md 中“ready-to-use commands ... at the bottom of each thread-N.md file”的出处。

从实现看,两个子命令背后的 API 路径不同,值得注意:

  • 回复replyToThreadL430-L496)先用 GraphQL 按线程 node ID 反查出 PR 编号与首条评论的 databaseId,再走 REST 的 POST /pulls/{pr}/comments/{commentId}/replies。源码注释解释了原因:该 REST 端点会立即发布回复,而 GraphQL 的 addPullRequestReviewThreadReply 可能把回复挂到未提交的草稿 review 上。回复正文会被自动加上 :robot: 前缀标识机器人身份。
  • 解决resolveThreadL498-L535)走 GraphQL 的 resolveReviewThread 变更,并回读 isResolved 校验结果,失败时打印警告而非静默。

闭环:flaky-only 失败时的重跑策略

当排障结论是“剩余失败全部为 Known Flaky Tests 且无需代码改动”时,SKILL.md 给出的收尾动作是:

gh run rerun <run-id> --failed

仅重跑失败 job,等待约 5 分钟后回到第 1 步重新运行 pr-status.js 分析;该循环最多重复 5 次。配合 workflow.md 的“flaky 只是历史上下文”规则,完整的判定链是:index.md 的 Known Flaky Tests 小节(跨 2+ 分支的历史失败)→ 确认本 job 失败项与之重合且无代码改动必要性 → 重跑验证 → 若仍失败则按真实失败继续调查。

小结

环节 动作 依据/工具
拉取状态 node scripts/pr-status.js [--wait] [PR] 生成 scripts/pr-status/results/index.md 及 job/thread 明细
定优先级 build → lint → types → tests → review workflow.md 优先级节
判定 flaky 只看 Known Flaky Tests 小节作参考,不自动免责 getFlakyTests 跨分支统计(scripts/pr-status.js#L1270-L1391
rust 失败 cargo fmt -- --check / cargo fmt workflow.md 常见模式节
lint 失败 pnpm prettier --write <file> + 仓库 lint 命令 package.json#L74-L75
测试失败 本地跑同一测试文件,模式/环境变量与 CI 对齐 package.json#L26-L39local-repro.md
Review 线程 reply-thread 说明改动,再 resolve-thread(或一步 reply-and-resolve-thread 现成命令见 results/thread-N.md 底部(scripts/pr-status.js#L1231-L1255
flaky-only 收尾 gh run rerun <run-id> --failed,最多循环 5 次 SKILL.md 第 7 步

整套流程的设计意图是把“CI 红”从一个模糊状态拆成可枚举的动作:报告文件给出全部定位信息,workflow.md 给出判定纪律与命令,而 scripts/pr-status.js 作为唯一事实来源持续刷新状态,保证每次决策都基于最新的 job 与线程数据。

登录后查看全文
热门项目推荐
相关项目推荐