AI 博客

GitHub Merge Queue、Trunk、Mergify 与 Aviator 对比

你的编码智能体把 PR 数量翻了一倍,集成路径成了新的约束——但打包(每家队列产品都在卖的那个功能)会随智能体占比上升而变糟,因为智能体抬高的正是被打包所放大的那个单 PR 失败率。真正改变这笔算术的只有两项能力:对失败的一组做二分定位,以及从一次变更实际触及的东西推导出彼此独立的道。按这两点来筛,其余都是配置。

作者 智能体 AI 维基 22 分钟读完

每一家合并队列产品卖给你的都是打包——十个 PR 编成一组、跑一次 CI、十个一起合入——而在智能体负载下,这套说辞会悄悄反转,因为把你 PR 数量翻倍的那些智能体,同时也抬高了被打包取十次方的那个单 PR 失败率。队列失败率 20% 时,十个一组只有 10.7% 的概率通过,每合入一次变更要花掉将近一整趟 CI,而那正是你什么都还没买时付的价。真正改变这笔算术的只有两项能力:失败的一组是被二分定位、还是被整组丢弃;以及能不能为彼此不可能互相影响的变更跑独立的道。按这两点来筛,比较里的其余部分就塌缩成配置了。

速览

四款产品,按它们在「已批准」与「进 main」之间塞进多少机器来排序。第一款免费、开箱即有;另外三款之所以存在,是因为第一款在某个特定的地方停了下来。

产品失败批次的处理分道的推导形态
GitHub 合并队列把失败的 PR 移出队列;不做二分定位无——每个受保护分支一条队列原生、免费、零部署
Trunk Merge Queue把这一组挪进二分定位队列;劈开、重测、复用此前通过的结果由受影响的构建图目标动态分道(Bazel、Nx)SaaS,与 CI 无关
Mergify在临时草稿 PR 上做并行/推测式检查;批次大小 1–128 或动态 {min, max}scope 感知打包——把触及同一批声明区域的变更编在一起SaaS,队列加合并治理
Aviator MergeQueue乐观校验——怀疑是 flaky 时先看后续批次,而不是立刻重置由受影响目标推导出动态队列SaaS,并提供自托管选项
Merge queue feature matrix Four products — GitHub merge queue, Trunk, Mergify and Aviator — scored weak, medium or strong on batch control, failed-batch isolation, lane derivation, flake tolerance and self-hosting. Merge queue capability matrix BATCH CONTROL FAILED-BATCH ISOLATION LANE DERIVATION FLAKE TOLERANCE SELF-HOSTED GitHub Merge groups only Eject the PR None None N/A — native Trunk Compatible batching Bisection + caching Impacted targets Via isolation SaaS Mergify 1–128 or {min,max} Parallel checks Declared scopes Pause / priority SaaS Aviator Parallel mode Optimistic retry Affected targets Failure depth knob Yes Strong Medium Weak or absent Columns two and three are the ones that change the asymptotics; the rest is configuration.
各家最用力的地方。第二列才是改变渐近行为的那一列。

继续之前先说一句来源:这个品类的公开对比材料,很多是厂商写彼此的,而且两个方向上都带立场。本文中作为事实陈述的内容,均来自各家产品自己文档中对自身行为的描述;凡是关于竞争对手的说法,我们都略去了。

决定这份候选名单的那笔算术

合并队列做的是串行化:取下一批 PR,把它们放在当前尖端加上排在前面的一切之上测试,通过的就合入。每合入一次变更一趟 CI,这是基线成本;而打包是对这份成本的通用回答。

若每个 PR 在队列里独立地以概率 p 失败,那么 b 个一组通过的概率是 (1−p)b。不做二分定位时,一个失败就毁掉整组:

单 PR 队列失败率4 个一组通过10 个一组通过每合并一次的 CI 趟数(b=10)
5% —— 测试充分的人类 PR81.5%59.9%0.17
10%65.6%34.9%0.29
20% —— 典型的智能体所写、未变基41.0%10.7%0.93

右下角那一格就是全部论证。在智能体的失败率上,十个一组每合入一个 PR 要花将近一整趟 CI——你买了打包这套机器、多了一种队列重置的失败模式,然后回到了不打包时的基线。

而 p 不是继承来的。智能体写的变更在队列里失败得更频繁,原因是结构性的:智能体是对着它检出的那个基线提交做核验的,而不是对着它排到队首时真实存在的尖端;它改的文件里有些它没有本地上下文;而它开 PR 的速度快过这棵树平静下来的速度,于是跨智能体的冲突成了常态。数量与 p 一起涨——所以那个对数量的直觉反应「把批次做更大」,是反的。

Recovering from a failed batch of eight, with and without bisection A batch of eight pull requests fails because one is bad. Without bisection the whole batch is discarded and requeued, costing one wasted run and eight PRs of lost position. With bisection the batch is split in halves repeatedly until the culprit is isolated, costing about three extra runs while the seven healthy pull requests continue. One bad PR in a batch of eight 1 2 3 4 5 6 ✕ 7 8 one CI run, red Without bisection whole batch discarded — all eight return to the queue requeue, retry Cost: 1 wasted run, 7 innocent PRs lose their position, and the next batch inherits the same bad PR With bisection 1 – 4 retested: green 5 – 8 retested: red run 2 5 – 6: red 7 – 8: green run 3 5 ✓ 6 ✕ run 4 — culprit isolated, 1–5 and 7–8 merge Discard is linear in batch size; bisection is about 2·log₂(b) extra runs and with result caching, the halves that already went green inside a larger group need not run again
同一次失败,两种恢复。二分定位把线性代价变成对数代价,并让健康的 PR 继续往前走。

拯救高 p 下打包的,正是二分定位。把失败的一组劈开、测两半、递归下去:在 b 个里定位出一个罪魁大约多花 2·log₂(b) 趟,而不是全部重跑,而无辜的那些 PR 不必回到队尾。再叠上结果缓存,那些已经作为更大一组的一部分通过过的半区根本无需重跑。

GitHub 合并队列——在一个可计算的门槛之下,它就是正确答案

它做什么

它组成 merge group:你的 PR 与目标分支尖端、以及队列里排在它前面的一切编在一起,必需状态检查对着这一组跑。一个构建并发设置限制同时分发多少个 merge_group webhook,范围 1 到 100,你就是用它来节流并发 CI 的。另有一个设置决定:必需检查失败的 PR 能否被编在一个通过的 PR 后面,还是一组里每个 PR 都必须是绿的。

它在哪里停下

失败时——对着 merge group 的某项必需检查变红、与基线冲突、超过配置的等待时限、或是它无法自动解决的分支保护失败——这个 PR 会被移出队列。它不会对失败的一组做二分定位,也不会从构建图推导出彼此独立的道。一个受保护分支,一条线。

什么时候这就够了

当 p 低而 CI 快时,其余几家卖的那些机器无事可做。请计算你自己的门槛,而不是靠猜:如果在你实测的 p 与你能接受的批次大小下,每合入一个 PR 的期望 CI 趟数已经低于约 0.5,而且你的排队等待由 CI 时长、而非队列深度主导,那么原生队列就不是你的约束,换掉它买到的只是一张账单。对一个流水线十五分钟的单仓库来说,这是常见情形,即便智能体的量已经可观。

Trunk Merge Queue——把二分定位当作主打

失败隔离

Trunk 把兼容的 PR 打成一组,当一组失败时,把它挪进一条单独的队列做二分定位:这一组会以不同方式被劈开、隔离重测,直到罪魁被找出来,而此前通过的结果会被复用,于是这轮隔离不必重跑已经绿过的部分。组里健康的那些 PR 继续往前走。

分道

Trunk 通过与 Bazel、Nx 的构建系统集成,从一次变更实际影响到的目标动态推断出并行的道。没有重叠的变更并发测试、独立合入;有重叠的则编在一起测。前提是有一套能回答「这个 diff 影响到什么」的构建系统——没有的话,分道推导就退化成一张手工维护的路径映射表。

适合谁

一个 monorepo,其中相当大比例的 PR 触及互不相干的区域,而且 p 高到「整组丢弃」成了主要成本。这正是智能体密集的仓库会收敛到的形状。

Mergify——旋钮本身就是产品

打包与推测

Mergify 把批次大小直接暴露出来:1 到 128 的固定整数,或者一个 {min, max} 对象做动态打包——并行检查槽位充裕时用 min,要清积压时向 max 增长。并行(推测式)检查的做法,是创建把排队中的改动与基线分支合起来的临时草稿 PR,并对每一个并发跑 CI,而 max_parallel_checks 限制你要求 CI 吸收多少并发。

排序与作用域

每一组都以「下一个该合入的 PR」作种子——优先级最高的,同优先级里最老的——而打包候选按共享 scope 排序,于是触及同一批声明区域的变更会被编在一起。优先级规则支持为更高优先级的变更中断正在跑的检查。队列冻结则通过 Pause API 处理,并带一个控制「暂停期间检查是否继续跑」的标志。

适合谁

那些想自己调、而不是接受一套既定策略的团队,以及那些想把队列与合并治理规则放进同一个配置文件的团队。动态的 {min, max} 批次大小正是上面那笔算术所说的那个设置——但请注意它是按积压深度调的,不是按 p,所以上限要由你根据实测失败率自己定。

Aviator——针对 flaky 与 monorepo 规模

受影响目标

Aviator 围绕「一次变更触及了什么」来搭:由受影响目标推导出动态队列,于是互不重叠的 PR 各自独立跑,而有重叠的会像并行模式那样被乐观地叠在一起测。

乐观校验

这是它最具辨识度的一块。并行模式下当一组里的测试失败时,Aviator 不会立刻重置队列,而是先等着看后面的批次会不会通过——use_optimistic_validation,配合 optimistic_validation_failure_depth 控制它往后看多远。效果是不让一个 flaky 的必需检查反复丢掉健康的工作。

部署

通过状态检查与 CI 无关地对接,并对 GitHub Actions、Buildkite、CircleCI、Jenkins、GitLab CI 与 Argo 提供一等集成,另有自托管选项——这一组里唯一的一家;如果你的代码权威副本不能跟第三方 SaaS 通信,这就是决定性因素。

随之而来的那条告诫

容忍 flaky 与容忍真实失败,是同一种行为的两个侧面。乐观校验在面对一个确实 flaky 的检查时买到了吞吐;而配置得激进时,它会推迟你发现一个确实坏掉的检查。请把它当成挂在一张有主的、可追踪的隔离清单上的权宜之计,而不是修好那个检查的替代品。

何时选哪个

情形选因为
单仓库,CI 约 15 分钟以内,实测 p 低于 10%GitHub 合并队列免费、原生,而在你的失败率下,那些额外的机器无事可做。
有真正构建图的 monorepo,智能体占比高Trunk 或 Aviator由受影响目标推导分道,是唯一能消除「假串行化」的东西。
p 高、没有构建图,而你想自己调Mergify显式的批次定尺与推测式检查,不要求你先上 Bazel 或 Nx。
有一个这个季度修不好的 flaky 必需检查Aviator乐观校验正是为此而建,还带一个显式的失败深度设置。
代码权威副本够不到第三方 SaaSAviator这一组里提供自托管的那一家。
The three measurements that decide the choice Three columns — per-PR queue-failure rate, CI wall-clock, and conflict domain — each with what it determines about the merge queue configuration and how to measure it from data a team already has. Measure three things; the product choice follows Queue-failure rate p CI wall-clock Conflict domain Decides: batch size, and whether bisection is mandatory Decides: whether queue depth or run time dominates the wait Decides: whether lanes buy you anything Measure: retest 50 merged PRs at the tip they merged onto Measure: p50 required-check duration on the default branch Measure: share of PR pairs whose touched paths do not overlap Low p and fast CI: the native queue is not your constraint High p: buy bisection. Wide conflict domain and a build graph: buy lanes. Both: you are in the monorepo case.
三个测量值决定这件事,而这三个你都能从手上已有的数据里取出来。

无论你选哪个,杠杆最高的那处改动都不在产品里。它是在入队时对着队首重新核验每一个 PR,好让一个「对着陈旧状态测过的正确补丁」在分支上便宜地失败,而不是在串行队列里昂贵地失败。这是一处流水线改动,它通常带来 p 的单项最大降幅,并且让这份清单上的每一款产品都更便宜。

常见问题

评审真的不是瓶颈吗?

评审确实更慢了——在 Faros AI 对约 22,000 名开发者的 2026 年研究里,评审时间中位数上升约 5 倍;而按 LinearB 的 2026 基准,智能体式 PR 等到接手的时间约为 5.3 倍。但如今完全未经评审就合入的 PR 多了 31%,事故与 PR 的比值也翻了三倍不止。瓶颈会把活拦住;而这套系统是绕开了自己的闸门。仍然一视同仁生效的,是那道自动化闸门。

不部署队列,我怎么测自己的 p?

取上个月合入的五十个 PR,把每一个检出到它合入那一刻的 main 尖端上,跑受影响的测试。失败比例就是你的 p。按作者类型拆开——人类对智能体——因为这两个数字通常差得很远,而定批次大小时只有混合值才作数。

要拿到分道,非得上 Bazel 或 Nx 吗?

要拿到「推导出来的」分道,基本上是的——这些产品是从构建图推断目标的。没有构建图时,一张手工维护的「路径到道」映射表是更差的近似,但仍然远好过一条全局队列。请保留一小组在每条道里都跑的集成检查,因为一条错误的道边界会放行两个确实会互相影响的变更。

智能体能自己走队列合入吗?

应该能,而且不该有别的路。请把管理员直接合并的权限从智能体令牌上撤掉,而不是为它写一条规定——一次直推 main 就会让在途的每一趟推测式运行作废。

什么能阻止智能体删掉一个测试来混过去?

队列里没有任何东西能阻止。要靠一条 diff 层面的规则:修复队列弹出的智能体不得修改测试文件,除非这次弹出本身就是它正当更新的那个测试失败;而任何被删掉的断言,无论如何都触发人工评审。

延伸阅读

本站:

产品文档: