把 Code Review 工具的判定结果作为 GitHub 分支保护规则的必过项,核心做法是让工具把结论写成”具名 Check Run”,再让分支保护按名字勾选它。要保留原有 PR 卡点和”不允许管理员绕过”策略,关键不在流程而在名字与并发模型:required check 绑定的是字符串上下文,名字一旦被改名、被矩阵展开、或者只跑在 push 而不跑在 pull_request 上,卡点会立刻失效。下面从 Check Run 的结论语义、分支保护必过项的绑定方式、不破坏现有策略的接入路径三方面展开。
一、Check Run 与 Status Check 的区别
Check Run API 是 GitHub 在 2018 年后引入的细粒度判定模型,相比旧的 Commit Status,多了三种关键能力:注释(annotation)、requested action(按钮)、以及非二元的结论状态。
| 维度 | Status Check(旧) | Check Run(新) |
|---|---|---|
| 结论状态 | success / failure / pending | success / failure / neutral / cancelled / timedout / actionrequired |
| 行内注释 | 不支持 | 支持,PR 文件视图直接高亮 |
| 互动按钮 | 不支持 | requested_action,可在 Checks 标签页提供”Fix this”等按钮 |
| 触发模型 | 只能绑 commit 状态 | 可订阅 check_suite 事件并按 head SHA 创建 |
| 谁可创建 | 任意集成 | 必须有 checks:write 权限的 GitHub App |
neutral 是 Code Review 工具最常用的结论——表示”评审过了一遍,没强制要求修改”,但也不当作 fail 阻止合并。CodeRabbit、Sourcery、Qodana 这类工具在评审完成后输出的就是 neutral 结论。action_required 才是阻止合并的信号,对应”我改了文件你必须重看”。
二、Check Run 名字如何与分支保护必过项绑定
GitHub 的 required status check 是一个字符串匹配:它必须与 workflow 报告的 status context(也就是 Check Run 的 name 字段)完全一致。任何不匹配都会让必过项停留在 expected 状态,PR 永远合并不了。
把 Code Review 工具接入分支保护时,必须遵守的工程化步骤:
- 在 Code Review 工具的 GitHub App 配置里指定一个稳定的 Check Run 名字,例如
code-review/quality,这个名字就是分支保护里要勾选的字符串; - 让工具只在
pull_request事件触发时跑,不依赖 push 事件,因为分支保护必过项只在 PR 上下文评估; - 在分支保护规则里勾选该名字,单独作为一行必过项,与原有的 CI 必过项并列;
- 关闭”Allow specified actors to bypass”中的管理员绕过项,保留”Include administrators”,确保 Review 卡点对所有角色生效;
- 开启”Require branches to be up to date before merging”前的
strict标志,让 Check Run 在 merge commit 上重跑,避免”PR 合在一起就坏”。
注意第 2 步,Code Review 工具如果只监听 push,Check Run 会出现在 head commit 上但不会进入 PR 必过项评估,分支保护根本看不到它。常见做法是工具同时监听 pull_request.opened 与 pull_request.synchronize,每次新 commit 推送都重出 Check Run。
三、Check Run 的并发模型与命名陷阱
工具在 Check Run 名字上的小改动,会让分支保护必过项整体失效。这是过去几年多个团队高频踩到的坑。
| 陷阱 | 现象 | 修复 |
|---|---|---|
改名 Check Run(如 code-review → code-review/quality) |
必过项停留在 expected,PR 不可合并 | 旧名作为别名保留一段过渡期,迁移完成后删除 |
| 矩阵任务未汇总 | 多个 matrix 腿产生多个 context,必过项勾不全 | 引入一个汇总 job 报告稳定名字,所有腿跑完才出 success |
| 工具跑了但 App 没装到目标仓 | 必过项根本不出现 | 把 App 安装到目标仓的权限范围里 |
| 工具只在 fork PR 上跑 | 主仓分支保护看不到结论 | 让工具在主仓与 fork 都创建 Check Run,并明确告诉用户要回到主仓的 PR 看结果 |
矩阵任务的坑尤其隐蔽。一个跑在 4 个操作系统上的 lint 任务会产出 4 个 context(lint / linux、lint / macos、lint / windows),分支保护要把这 4 个都勾上才算过。Code Review 工具更不该用矩阵——评审只有一份报告,应当只出一个 context。
四、不破坏现有分支保护策略的轻量改造路径
如果仓库已经有 CI 必过项、CODEOWNERS 必审、线性历史等设置,新增 Code Review 必过项应当做加法而不是替换。
原分支保护规则(main 分支)
├── Require pull request reviews before merging
│ ├── Required approving reviews: 2
│ └── Require review from Code Owners
├── Require status checks to pass before merging
│ ├── ci / unit-tests
│ ├── ci / integration
│ └── security / sast
└── Require linear history
新增 Code Review 必过项后
└── Require status checks to pass before merging
├── ci / unit-tests
├── ci / integration
├── security / sast
└── code-review / quality ← 新增,单独的必过项
轻量改造路径是:在 Require status checks to pass before merging 的列表里追加一行,复用原有 2 名 reviewer、CODEOWNERS、线性历史等设置,不改其他项。这种加法不会让 PR 合并条件突然收紧导致存量分支被卡住——所有旧 PR 只需补跑新 Check Run。
五、落地示例:用 GitHub App 创建 Check Run
下面给出一段可复用的 Node.js 片段,演示如何让 Code Review 工具把结论写成具名 Check Run。这段解决的是”工具报告完了结果在 PR 看不到”的核心问题。
// 监听 check_suite 事件,对新 commit 创建名为 code-review/quality 的 Check Run
app.on("check_suite.requested", async (context) => {
const { head_sha } = context.payload.check_suite;
const params = {
owner: context.payload.repository.owner.login,
repo: context.payload.repository.name,
head_sha,
name: "code-review/quality",
status: "in_progress",
started_at: new Date().toISOString(),
output: {
title: "Code Review",
summary: "评审进行中,等待分析完成",
},
};
await context.octokit.checks.create(params);
});
// 评审完成后更新结论,neutral 表示"看过了,没强制要求"
app.on("check_run.rerequested", async (context) => {
const conclusion = runReviewEngine();
await context.octokit.checks.update({
owner: context.payload.repository.owner.login,
repo: context.payload.repository.name,
check_run_id: context.payload.check_run.id,
status: "completed",
conclusion, // success | failure | neutral | action_required
completed_at: new Date().toISOString(),
});
});
代码块前后注意:前一句在说明”为什么这么写”——把 name 字段写死成 code-review/quality 是为了和分支保护必过项字符串对齐;后一句在提醒——conclusion 选 neutral 还是 action_required,决定了 Code Review 工具究竟是”建议性反馈”还是”硬性卡点”。如果选错了,PR 要么合并不了,要么全无阻拦。
六、常见问题(FAQ)
Q1:Code Review 工具的 Check Run 跑出来了,但分支保护里看不到怎么办?
首先确认 GitHub App 已安装到目标仓库并启用 checks:write 权限,再确认 Check Run 是 pull_request 触发的而不是仅 push。
Q2:把 Code Review 设成必过项后,老 PR 都卡住合并不了,怎么处理?
必过项是按字符串匹配的,给老 PR 重推一个 commit 触发 Check Run 重跑即可,不必手动改规则。
Q3:矩阵任务产生的多个 context 都要勾选吗?
不需要。给 Code Review 这种单一报告类型加一个汇总 job,所有分析完才出一个稳定 context,分支保护只勾这一个名字。