Next.js CI 分诊工作流:从 pr-status.js 报告到 Review Thread 闭环的处理手册
【免费下载链接】next.jsThe React Framework项目地址: https://gitcode.com/GitHub_Trending/next/next.js
本篇基于 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 定义了严格的全局优先级:
- Build failures(构建失败)
- Lint failures(Lint 失败)
- Type failures(类型检查失败)
- Test failures(测试失败)
- 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.js 的runAnalysis流程会先清理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 中有明确的实现对应:
"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——报告本身也只给概率性提示,最终判定权仍在调查者手里,这与“不作为自动免责理由”的规则一致。flaky 判定算法:
getFlakyTests会抓取最近 5 次其他分支的失败 run,并行拉取其中失败 job 的日志,统计“在 2 个及以上不同分支上都失败过”的测试路径,只有满足该条件才进入 flaky 集合(L1270-L1391)。其中还有两个保护性细节:单次 run 失败 job 超过 20 个时视为系统性故障而非 flaky(L1315-L1316);当前 PR 所在分支被排除以免自我匹配(L1297)。失败结论的认定范围:脚本把
failure、timed_out、startup_failure三种结论都计入失败(FAILED_CONCLUSIONS,L266),即超时与启动失败同样进入 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-checkjob 通过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 给出两条规则:
- 本地运行与 CI 完全一致的失败测试文件;
- 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 build再next start后对产物做断言,两者在模块解析、缓存行为上可能产生差异,模式不匹配时的本地通过/失败结论都不可信。
进一步地,CI job 往往还叠加了特性开关环境变量。scripts/pr-status.js 的getJobEnvVarsFromWorkflow会解析 .github/workflows/build_and_test.yml 中每个 job 的afterBuild块,提取其中的export VAR=value语句,并按 job 显示名前缀匹配后写入index.md的### Job Environment Variables小节(L154-L209、L829-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 结果(extractTestOutputJson,L541-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-L663、L1111-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 线程的处理规程:当完成了评论要求的代码改动,或确认当前代码已满足该评论时:
- 先回复线程,说明所采取的动作:
node scripts/pr-status.js reply-thread <threadNodeId> "Done -- <description of changes>"- 再解决(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 eachthread-N.mdfile”的出处。
从实现看,两个子命令背后的 API 路径不同,值得注意:
- 回复(
replyToThread,L430-L496)先用 GraphQL 按线程 node ID 反查出 PR 编号与首条评论的databaseId,再走 REST 的POST /pulls/{pr}/comments/{commentId}/replies。源码注释解释了原因:该 REST 端点会立即发布回复,而 GraphQL 的addPullRequestReviewThreadReply可能把回复挂到未提交的草稿 review 上。回复正文会被自动加上:robot:前缀标识机器人身份。 - 解决(
resolveThread,L498-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-L39、local-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 与线程数据。
【免费下载链接】next.jsThe React Framework项目地址: https://gitcode.com/GitHub_Trending/next/next.js
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考