Next.js CI 分诊工作流:从 pr-status.js 报告到 Review Thread 闭环的处理手册
本篇基于 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-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 给出两条规则:
- 本地运行与 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 each thread-N.md file”的出处。
从实现看,两个子命令背后的 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 与线程数据。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00