Home Assistant core 合并队列分诊:基于 ha-merge-queue Skill 的 PR 就绪性核查方法
本文围绕 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 + 正文结构,name 为 ha-merge-queue,description 明确其职责:
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)清单——每一项都标注唯一的具体解锁动作。
需要强调两条纪律边界,它们由仓库根目录的治理文件背书:
- 只在控制台汇报,绝不在 GitHub 上执行动作——不评论、不评审、不合并、不向贡献者分支推送。这与 AI_POLICY.md 一致:该项目不允许自治代理参与贡献,"a human decides and acts"(由人来决策与行动)。
- 绝不因为 CI 全绿就宣布 PR 就绪——必须阅读短名单中每个 PR 的 diff,因为 CI 无法告诉你这个改动是否正确、是否被需要。
同一技能目录下还有 ha-pr-reviewer、ha-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。
这里有两条关键经验,直接决定了整个流程的可靠性:
- "跑多条查询并合并结果"——没有任何一条查询能覆盖全部情况,单一查询会漏掉候选;
- 搜索索引滞后真实状态数小时——
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必须是success、skipped或neutral。
其余一切值都构成淘汰理由:failure、cancelled、timed_out、action_required、stale,以及任何仍处于 queued 或 in_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— 存在合并冲突。作者需要把devmerge 进自己的分支。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 + 重新生成文件"两步;- 其他任何值(
draft、has_hooks、未列出的值)— 不要猜它的含义。把 PR 当作未就绪,并如实报告拿到的值。
检查 4:Review submissions(评审提交)
关键原则:要拉取 reviews 本身,而不只是 threads。CHANGES_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 参与度。合并后形成四档:
- Clean PR 且勾选了
[x] I have reviewed two other open pull requests...; - Clean PR 但未勾选;
- Blocked / near-miss PR 且已勾选;
- 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红——点名该补的标签(bugfix、new-feature等);- 等待 code-owner 批准——从集成的
manifest.json中点名代码所有者(code owners 的权威映射维护在仓库根目录的 CODEOWNERS 中,core 的 CODEOWNERS 达百 KB 级别,覆盖几乎每个集成目录); - 悬置的
CHANGES_REQUESTED——点名评审者及其具体要求; dirty——作者必须 mergedev并重新生成所有生成文件。
Skill 还要求主动指出值得维护者注意的不一致:一个正文声称包含 breaking change、却没有带 breaking-change 标签的 PR,会悄无声息地从发布说明(release notes)中漏掉——这类信号无法从任何单项状态里读出来,只能靠交叉比对 PR 正文与标签。
方法论总结:为什么这套检查链是可靠的
把 SKILL.md 的设计拆开看,它实际上是一份"GitHub 状态数据的可信度分级手册":
- 搜索索引(最不可信):滞后数小时,只做候选粗筛;
- Combined status / merge-gate(可信、且是合并按钮的真实依据):但只覆盖 commit statuses,不覆盖 Actions;
- Check runs(实时):必须逐条、分页读到底,终态白名单只有
success/skipped/neutral; mergeable_state(异步计算):unknown不可乐观化,blocked不可单一归因,dirty只能 merge 不能 rebase(AGENTS.md 约束);- Reviews / Threads(实时、且互不覆盖):
CHANGES_REQUESTED只在 reviews 里可见;resolve 线程不撤销评审; - PR 正文(人工声明):peer-review 复选框做优先级加权,breaking-change 声明与标签做一致性核对。
再叠加两条行为红线——只汇报不执行(AI_POLICY.md 要求人类决策与行动)、必须读 diff(CI 证明不了"改动是对的且被需要的")——这套流程给出的结论才能同时满足"技术上可合并"与"工程上值得合并"两个维度。
适用前提与限制:该方法论绑定 GitHub 的 API 语义(mergeable_state、check runs 分页、combined status 上下文),以及 home-assistant/core 特有的合并门禁上下文(code-owner-approval、required-labels、docs-missing 等由仓库侧 CI 注册);移植到其他仓库时,第一、二项检查中的上下文名单需要按目标仓库的分支保护配置重新梳理,而分页纪律、终态白名单和"索引滞后"三原则则普遍适用。
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