首页
/ Home Assistant core 合并队列分诊:基于 ha-merge-queue Skill 的 PR 就绪性核查方法

Home Assistant core 合并队列分诊:基于 ha-merge-queue Skill 的 PR 就绪性核查方法

2026-09-04 12:43:20作者:董宙帆

本文围绕 ha-merge-queue 这一 Claude Code Skill 展开,完整拆解它如何为 home-assistant/core 维护者从成百上千个开放 PR 中筛出"可立即合并"的候选,并解释每一步背后的 GitHub 机制——合并门禁状态(merge-gate statuses)、check runs 终态判定、mergeable_state 语义、评审提交与评审线程的区分、以及基于 PR 模板中 peer-review 复选框的优先级加权。读完本文,你将掌握一套可复制的 PR 合并队列分诊(merge-queue triage)方法论,并能理解为什么"CI 全绿"远不等于"可以合并"。

这个 Skill 在仓库中的定位

.claude/skills/ha-merge-queue/SKILL.md 是仓库内为 AI 编码代理编写的"技能定义"文件,采用 frontmatter + 正文结构,nameha-merge-queuedescription 明确其职责:

Finds open Home Assistant pull requests that are genuinely ready to merge, checking CI, the merge-gate statuses, code-owner approval, merge conflicts, requested changes and unresolved review threads.

它的产出目标非常具体:一份维护者可以立刻批准并合并的开放 PR 短名单(默认 10 个候选),外加一份"差一步"(near-miss)清单——每一项都标注唯一的具体解锁动作

需要强调两条纪律边界,它们由仓库根目录的治理文件背书:

  1. 只在控制台汇报,绝不在 GitHub 上执行动作——不评论、不评审、不合并、不向贡献者分支推送。这与 AI_POLICY.md 一致:该项目不允许自治代理参与贡献,"a human decides and acts"(由人来决策与行动)。
  2. 绝不因为 CI 全绿就宣布 PR 就绪——必须阅读短名单中每个 PR 的 diff,因为 CI 无法告诉你这个改动是否正确、是否被需要。

同一技能目录下还有 ha-pr-reviewerha-review 等姊妹技能,分别负责代码评审与队列核查的前置准备;ha-merge-queue 专注"合并前的最后一道门"。

第一步:收集候选(Gather candidates)

Skill 给出的基础搜索查询是:

repo:home-assistant/core is:open is:pr draft:false status:success -review:changes_requested
  -label:"awaiting-frontend" -label:"stale" -label:"cla-needed"

各过滤条件的含义:

  • draft:false:排除草稿 PR,排除结构性不可合并的对象;
  • status:success:只保留搜索索引中 CI 为绿的 PR;
  • -review:changes_requested:排除携带"要求修改"评审的 PR;
  • -label:"awaiting-frontend"-label:"stale"-label:"cla-needed":排除等待前端、已被标记陈旧、尚缺 CLA 的 PR。

Skill 还给出两个可选的定向变体:

  • 追加 label:"small-pr" 找"quick wins"(小而快的合并);
  • 追加 label:"code-owner-approved"review:approved,找已携带批准的 PR。

这里有两条关键经验,直接决定了整个流程的可靠性:

  1. "跑多条查询并合并结果"——没有任何一条查询能覆盖全部情况,单一查询会漏掉候选;
  2. 搜索索引滞后真实状态数小时——status: 只能当作粗筛过滤器,永远不能当作证据。每一个入围者都必须用下文六个逐项检查来复核。

这一点对所有依赖 GitHub 搜索做分诊的团队都是通用教训:status:success 是索引快照,而 check runs 是实时 API 数据,两者的时差足以让一个已经失败的测试在索引里仍显示为绿色。

第二步:对每个入围者做六项核查

Skill 要求检查全部六个条件,第 1 至第 5 项任何一项失败,PR 即出局。在开始检查之前,有一条贯穿始终的操作要求:

每个列表型响应都要分页取完再下结论。 check runs、reviews 和 review threads 都是分页返回的,每页通常 30 条;而 Home Assistant 的全套 CI 一次就有 40 多条 check runs——所以第一页展示的可能只是一个"绿色子集",而失败项或遗留评审藏在第二页。必须把返回条数与报告的总数对比,持续翻页直到两者一致。

从仓库的 CI 配置 ci.yaml 可以看到,core 的流水线包含 pytest、mypy、pylint、yamllint 等多个独立 job,check runs 数量超过单页上限是常态——这条分页纪律不是理论要求,而是针对该仓库 CI 规模的针对性防御。

检查 1:Merge-gate statuses(合并门禁状态)

获取 commit 的 combined status,这是"最便宜、信息量最大"的调用,而且它才是真正挡住合并按钮的东西。有五个上下文(context)值得关注:

状态上下文 作用 变红的典型原因
code-owner-approval Platinum 级集成必需 尚未获得代码所有者批准
required-labels 强制标签检查 作者在 PR 模板中一个"Type of change"复选框都没勾
docs-missing 文档缺失检查 面向用户的功能改动却没有关联文档 PR
cla-bot CLA 检查 贡献者未签署/未通过 CLA
blocking-label-awaiting-frontend 前端依赖检查 PR 需要前端改动但尚未就绪

这些门禁与 PR 模板 PULL_REQUEST_TEMPLATE.md 一一对应:模板要求"check only 1 box"的 Type of change 复选框(Dependency upgrade / Bugfix / New integration / New feature / Deprecation / Breaking change / Code quality improvements)正是 required-labels 的判定来源;模板中的 "Link to documentation pull request" 字段则与 docs-missing 联动。也就是说,维护者看到的红色状态,绝大多数能回溯到模板中某个未填写的字段——这正是 Skill 要求 near-miss 清单"给出唯一的解锁动作"(比如"补勾 bugfix 标签")的依据。

检查 2:Check runs(GitHub Actions 结果)

commit statuses 不覆盖 GitHub Actions,必须单独拉取 check runs,且要求每一条都到达可接受的终态

  • status 必须是 completed
  • conclusion 必须是 successskippedneutral

其余一切值都构成淘汰理由:failurecancelledtimed_outaction_requiredstale,以及任何仍处于 queuedin_progress 的运行。Skill 特别指出一个高频陷阱:"combined status 是绿的,底下却有一个红色或仍在运行的测试 job"是很常见的——所以只看状态列表永远不够,必须逐条核对 conclusion。

检查 3:Merge conflicts(mergeable_state

读取 mergeable_state,并理解 GitHub 的异步计算模型:请求若找不到缓存答案会返回 unknown 并触发计算,而紧随其后的下一次请求仍可能返回 unknown。正确姿势是有界重试轮询——尝试几次、中间停顿——始终不解析的 unknown 按"未就绪"处理,而不是默认它没问题

各取值的完整语义(Skill 原文的核心资产):

  • clean — 所有合并要求均已满足,现在就可以合并;
  • blocked — 某个分支保护要求未满足。它不特指"等批准",不要默认这么报告:应该用其余四项检查定位到底是哪个要求缺失,只有当它们全部干净时,才可以把它归结为"awaiting review";
  • behind — 基分支(dev)前进了。这是可操作状态且往往是一键更新,所以应当列出并展示而不是丢弃该 PR;
  • unstable — 某个非必需 check 在失败。列入短名单之前先查明是哪一个;
  • dirty — 存在合并冲突。作者需要dev merge 进自己的分支。Skill 明确警告:不要让人 rebase——AGENTS.md 第 7 行规定:"Do NOT amend, squash, or rebase commits that have already been pushed to the PR branch after the PR is opened",因为评审者需要跟踪提交历史、需要看到自上次评审以来发生了什么。另外,homeassistant/generated/integrations.json 这类生成文件持续冲突,所以新增集成的 PR 很快就会变脏,这类 PR 需要"merge dev + 重新生成文件"两步;
  • 其他任何值drafthas_hooks、未列出的值)— 不要猜它的含义。把 PR 当作未就绪,并如实报告拿到的值。

检查 4:Review submissions(评审提交)

关键原则:要拉取 reviews 本身,而不只是 threadsCHANGES_REQUESTED 评审只出现在这里;而且评审者完全可能只写一段顶层正文、不留任何 inline 评论就要求修改——这种 PR 的开放 review threads 数量为零,在"线程检查"下看起来干干净净。

判定规则:

  • 按评审者取其最新一次提交(latest submission per reviewer);
  • 一个悬而未决的 CHANGES_REQUESTED,如果之后没有同一人的 APPROVED 来覆盖它,就淘汰该 PR——无论其 inline 线程后来是否被 resolve:resolve 线程不等于撤销评审;
  • 不要拿 -review:changes_requested 搜索限定符来替代这一步——索引是旧的,且 mergeable_state: blocked 无法区分"缺批准"和"被要求修改"两种情况。

检查 5:Review threads(评审线程)

拉取 review threads 并读取 is_resolved。判定标准只有一条:线程是否已 resolve、或实质上已被处理——绝不按"谁写的"来豁免。

Skill 在此点了一个具体对象:copilot-pull-request-reviewer 的未解决 finding 是一份 bug 报告,可能是真实缺陷,必须按内容本身评判后再决定是否采信。每个仍然开放的线程都要逐条说明"它是什么、以及为什么它(不)构成阻碍"——豁免的合法理由只有:问题已被回答、建议已被考虑并明确拒绝、该点已在 diff 的其他位置修复。原文的底线是:未处理的缺陷就是阻碍项,不管有没有人在为它争论,不能因为线程"看起来属于哪一类"就一笔带过。

检查 6:Peer-Review Checklist 核验(优先级加权,非淘汰项)

这项检查不淘汰 PR,而是调优先级。检查 PR 正文中 PR 模板里的复选框:

- [x] I have reviewed two other [open pull requests][prs] in this repository.

该复选框真实存在于 PULL_REQUEST_TEMPLATE.md 第 105 行,位于 "To help with the load of incoming pull requests:" 小节之下——core 仓库的评审积压是长期结构性问题,模板鼓励提交者去评审另外两个开放 PR 以分担评审负载。

  • 勾选([x]:在排序中给予更高优先级,奖励那些帮助清理评审积压的贡献者;
  • 未勾选([ ] 或不存在):以标准优先级留在队列中,不淘汰

第三步:报告与排序

候选按两级标准排序:首要标准是状态就绪度clean 在前,其次是除目标项外全绿的 blocked),次要标准是 peer-review 参与度。合并后形成四档:

  1. Clean PR 且勾选了 [x] I have reviewed two other open pull requests...
  2. Clean PR 但未勾选;
  3. Blocked / near-miss PR 且已勾选;
  4. Blocked / near-miss PR 且未勾选。

每个 PR 的报告要素包括:

  • PR 编号写成完整 markdown 链接
  • 涉及的集成或核心领域;
  • 一句话说明它做了什么;
  • 当前的阻塞状态;
  • 若作者勾选了 peer-review 框,明确标注(示例格式:"⭐ Contributor reviewed 2 PRs")。

Skill 还要求不要遗漏"没有集成"的 PR:大量 core PR 触碰的是 helpers、框架、recorder 或仓库工具,根本没有对应集成——此时应如实命名它触碰的东西,而不是丢弃或硬编一个集成名。

Near-miss 清单:每种阻塞对应唯一动作

near-miss 单独成列,每项只给"解锁它的哪一个动作"。Skill 归纳的高频模式:

  • 与 diff 无关的测试失败——点名是哪个测试,说"重跑该 job";
  • required-labels 红——点名该补的标签(bugfixnew-feature 等);
  • 等待 code-owner 批准——从集成的 manifest.json 中点名代码所有者(code owners 的权威映射维护在仓库根目录的 CODEOWNERS 中,core 的 CODEOWNERS 达百 KB 级别,覆盖几乎每个集成目录);
  • 悬置的 CHANGES_REQUESTED——点名评审者及其具体要求;
  • dirty——作者必须 merge dev 并重新生成所有生成文件。

Skill 还要求主动指出值得维护者注意的不一致:一个正文声称包含 breaking change、却没有带 breaking-change 标签的 PR,会悄无声息地从发布说明(release notes)中漏掉——这类信号无法从任何单项状态里读出来,只能靠交叉比对 PR 正文与标签。

方法论总结:为什么这套检查链是可靠的

SKILL.md 的设计拆开看,它实际上是一份"GitHub 状态数据的可信度分级手册":

  1. 搜索索引(最不可信):滞后数小时,只做候选粗筛;
  2. Combined status / merge-gate(可信、且是合并按钮的真实依据):但只覆盖 commit statuses,不覆盖 Actions;
  3. Check runs(实时):必须逐条、分页读到底,终态白名单只有 success/skipped/neutral
  4. mergeable_state(异步计算):unknown 不可乐观化,blocked 不可单一归因,dirty 只能 merge 不能 rebase(AGENTS.md 约束);
  5. Reviews / Threads(实时、且互不覆盖):CHANGES_REQUESTED 只在 reviews 里可见;resolve 线程不撤销评审;
  6. PR 正文(人工声明):peer-review 复选框做优先级加权,breaking-change 声明与标签做一致性核对。

再叠加两条行为红线——只汇报不执行(AI_POLICY.md 要求人类决策与行动)、必须读 diff(CI 证明不了"改动是对的且被需要的")——这套流程给出的结论才能同时满足"技术上可合并"与"工程上值得合并"两个维度。

适用前提与限制:该方法论绑定 GitHub 的 API 语义(mergeable_state、check runs 分页、combined status 上下文),以及 home-assistant/core 特有的合并门禁上下文(code-owner-approvalrequired-labelsdocs-missing 等由仓库侧 CI 注册);移植到其他仓库时,第一、二项检查中的上下文名单需要按目标仓库的分支保护配置重新梳理,而分页纪律、终态白名单和"索引滞后"三原则则普遍适用。

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