Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

373 linesCodeBlameRaw
1---
2title: Merge queue
3description: Test each pull request together with the ones ahead of it, so main only moves to a state whose required checks passed.
4---
5
6Merging one pull request at a time, each caught up with `main`, keeps every
7merge clean as text. It does not prove the result works: two changes can
8merge without a conflict and still break each other. With the merge queue
9on, a pull request is tested together with everything ahead of it before it
10lands, and `main` only ever moves to a state whose required checks passed.
11
12g1t builds the queue's states in the repository itself, without a
13sandbox. What checks them is the repository's `merge_group`
14[workflows](/guides/actions/), which run and are billed like any other
15workflow run.
16
17## Turn it on
18
191. Open the project's **Settings → Rules**. You need the Admin
20 [role](/guides/access-and-roles/) on its repository.
212. Open the ruleset that covers the default branch, or create one.
223. Choose **Add a rule**, then **Require the merge queue**. Set how many
23 pull requests it tests at once, the smallest batch it starts with and
24 how long it waits for one, and how long a batch's checks may take.
254. Save.
265. Add `merge_group` to the `on:` of every workflow behind a
27 [required status check](/guides/pull-requests/#required-status-checks),
28 so that it runs on the queue's states too
29 ([below](#what-each-state-is-held-to)).
30
31From the API, send `merge_queue` to `PATCH /repos/{owner}/{name}/settings`
32(or `update_repo_settings`), which adds the rule to the "Default branch
33protection" [ruleset](/guides/rules/):
34
35```sh
36curl -X PATCH https://api.g1t.sh/repos/acme/web/settings \
37 -H "Authorization: Bearer $G1T_TOKEN" \
38 -H "Content-Type: application/json" \
39 -d '{"merge_queue": true}'
40```
41
42## What merging does with the queue on
43
44Merging a pull request, from its page (**Add to the merge queue**), with
45the `pull_request` tool's `merge` action, or with
46`POST /repos/{owner}/{name}/pulls/{number}/merge`,
47adds it to the queue instead of changing `main`. Everything a merge needs
48is still checked first: the pull request must be ready for review, every
49[required check](/guides/pull-requests/#required-status-checks) must have
50passed on its head, and it must have the approvals the repository asks
51for, including its [code owners'](/guides/codeowners/#require-review-from-code-owners)
52when it requires them. Only people with the Write [role](/guides/access-and-roles/) or higher can add to the
53queue. Merging a pull
54request that is already queued changes nothing.
55
56The pull request's conversation records who added it, and its page shows
57where it is in the queue. Anyone who can merge can take it out with **Remove from the
58queue**. Closing a pull request also takes it out.
59
60## How entries are tested
61
62g1t takes up to four entries (the rule's `max_entries_to_build`) from the
63front of the queue and tests them all at once, speculatively. Each entry's
64state is the one before it with that entry landed on top, in queue order:
65
66| Entry | Tested as |
67| --- | --- |
68| 1st | `main` + #41 |
69| 2nd | `main` + #41 + #44 |
70| 3rd | `main` + #41 + #44 + #46 |
71| 4th | `main` + #41 + #44 + #46 + #47 |
72
73Each entry is landed on the state ahead of it by its own
74[merge method](/guides/pull-requests/#merge-methods), exactly as merging it
75there would:
76
77- **Merge commit**: a merge commit, with the message for the repository's
78 merge commits or the one written when it was queued.
79- **Squash**: one commit, by the pull request's author, with the squash
80 message and its `Co-authored-by:` trailers.
81- **Rebase**: each of its commits made again on top, keeping their authors.
82- **Fast-forward**: its own head, which must already contain everything
83 ahead of it; otherwise it fails, saying to bring it up to date or merge
84 it another way.
85
86The method is the one asked for when it was merged into the queue (the
87merge box's choice, `merge_method` in the API), else the merge queue
88rule's `merge_method` (merge commit unless set). Both must be methods the
89repository and the branch's [rules](/guides/rules/) allow, or merging is
90refused, saying which are; linear history rules out merge commits.
91A commit message written then (`commit_title`, `commit_message`) is kept
92with the entry; otherwise the repository's default message is used, worked
93out when the entry is first built. Sign-off is added where the repository
94requires it. The same limits apply as to merging directly: g1t makes at
95most 1,000 changed files, 100 files merged line by line and 250 rebased
96commits itself.
97
98If every entry passes, the four can land one after another without being
99tested again. The next batch starts when nothing is being tested. A batch
100that takes longer than 45 minutes (`check_response_timeout_minutes`) is tested again.
101
102### What each state is held to
103
104g1t pushes each state it built to a branch of its own, `g1t-queue/<entry>-<attempt>`,
105and runs the repository's [workflows](/guides/actions/) that run on
106`merge_group` on it. The entry waits for them, and passes only if:
107
108- every `merge_group` workflow it started passed; and
109- every [required status check](/guides/pull-requests/#required-status-checks)
110 of the default branch passed on that commit.
111
112So a change that breaks something another change ahead of it relies on is
113caught here, even when it merges without a conflict and its own checks
114passed. The branch is deleted once the entry lands or leaves the queue.
115
116A workflow opts in like this:
117
118```yaml
119on:
120 pull_request:
121 merge_group:
122```
123
124Required checks only report on a queued state if their workflows run on
125`merge_group`. When the branch requires checks and no workflow runs on
126`merge_group`, the entry fails with a message saying so: "the required
127check CI cannot report on it: no workflow runs on merge_group events. Add
128merge_group to the on: of the workflows the branch requires". A required
129check that a workflow did not report on the state fails it the same way.
130A repository that requires no checks and has no `merge_group` workflows
131only has each state built: an entry passes once it merges cleanly with
132what is ahead of it.
133
134Each build has its own attempt token. A result from an old attempt cannot
135replace a retry or put a removed entry back in the queue. Workflow results
136apply only to the combined commit still being checked.
137Retries use separate Git branches, so a late build cannot overwrite the
138new attempt's branch. Workflow results wait until the whole group has been
139registered; a dispatch error does not count as a pass.
140
141## How entries land
142
143Entries land in order. When an entry has passed and everything ahead of it
144has landed, `main` moves to exactly the state that was tested: since each
145state was built on the one ahead of it, that is a fast-forward to the
146commits its method made, which are the commits its checks ran on. The pull
147request is recorded as merged by its method, with the commit `main` moved
148to as its merge commit. The issue closes and the other pull requests for
149it are superseded, as with any merge.
150
151Current reviews and branch rules are checked again before publication,
152against the combined commit's checks and the entry's merge method. Once publication starts, removal,
153closing or conversion to draft returns a conflict until it finishes. If the
154service stops after updating Git, the next queue poll resumes bookkeeping
155for that same commit instead of publishing a different branch tip.
156
157Before landing, g1t checks that nothing has changed underneath:
158
159- If the pull request was pushed to after it was tested, it and the entries
160 tested on top of it are tested again.
161- If `main` moved outside the queue, every entry is tested again on the new
162 `main`.
163
164A pull request that is already known to conflict with `main` is not added
165to the queue: [its merge box](/guides/pull-requests/#conflicts) says which
166files conflict and how to resolve them first. One that is only behind `main`
167does not need to catch up to join the queue, since the queue tests it on
168top of `main`. Where the repository requires pull requests to be up to
169date, [catch it up](/guides/pull-requests/#keeping-it-up-to-date) first: when it and
170`main` did not change the same lines, that takes a few seconds.
171
172## When an entry fails
173
174An entry fails when its `merge_group` workflows or required checks fail
175on the combined state, when its change clashes with what is ahead of it, or
176when the state cannot be built (a fast-forward that does not contain what
177is ahead, or more than g1t makes itself). One that cannot be built leaves
178the queue, and the entries behind it are built in the next batch without
179it.
180
181### Who answers for it
182
183A state that fails while an entry ahead of it is still being tested is
184held until those ahead are settled. If one of them fails too, the held
185entry is tested again without it and is not blamed. Once every entry ahead
186has passed, the held entry answers for the failure. So when #41 is
187broken, it leaves the queue, and #44 and #46, which were tested with #41
188in them, are tested again without it rather than taken out with it.
189
190The entry that answers for a failure leaves the queue, and g1t works out
191what it failed with:
192
193| What happened | Who it failed with (`suspects`) |
194| --- | --- |
195| Tested on `main` alone, and its own checks fail on its head too | Nobody: the change is the cause. |
196| Tested on `main` alone, though its own checks pass on its head | Nobody: the change answers for it, and the note names what landed on `main` since it branched (`landed`), [below](#when-main-moved-under-it). |
197| Its own checks fail on its head too | Nobody: the change is the cause. |
198| It conflicts with an entry ahead | That entry, and the conflicting files. |
199| A required check did not report, or the state could not be built | Nobody: the note says what to fix. |
200| Its own checks pass, one entry ahead | That entry. |
201| Its own checks pass, several entries ahead | Found by building smaller combinations, below. |
202
203### When `main` moved under it
204
205An entry with nothing ahead of it is tested on `main` as it is now. When
206its own checks pass on its head but a check fails once it is merged into
207`main`, something that landed on `main` after the pull request branched is
208what it now breaks with: #1 changed a function's signature, say, and this
209pull request, written before #1 landed, still calls the old one.
210
211The entry still answers for the failure, since it is the one that has to
212adapt, and nothing extra is built. g1t looks at the 50 pull requests most
213recently merged into `main` before the entry was tested, keeps those whose
214commits the entry's history (its latest 1,000 commits) does not hold, and
215reports up to ten of them, newest first, as `landed`. A pull request that
216was brought up to date with `main` since it branched holds what it merged,
217so only what landed after that is named. The note on the pull request then
218reads, for example:
219
220> g1t traced its merge queue failure to the default branch as it is now:
221> `CI / merge_group` failed with this change merged into it, though this
222> passes its checks on its own. Since this branched, #1 landed and is the
223> likely cause. Bring it up to date with the default branch, fix what
224> breaks, and push. Push a fix: once its checks pass on the new commit, it
225> rejoins the merge queue by itself.
226
227When only one pull request landed it is named as the likely cause; when
228several did, they are listed as what it may conflict with. When none is
229found (`main` moved by a push rather than a pull request, say), the note
230says the change is the cause, as before. A conflict, a required check that
231did not report and a state that could not be built never look this up.
232
233### Narrowing it down
234
235When an entry passes its own checks but fails combined with several entries
236ahead, g1t tests smaller combinations using the same `merge_group` workflows.
237A failing subset becomes the new suspect set. A passing subset causes g1t
238to test its complement before excluding anything. If both subsets pass,
239g1t retains the last failing combination: the failure may involve several
240changes together, or may not be deterministic. Diagnosis stops after three
241extra builds, retaining the last combination demonstrated to fail.
242
243These combinations are built as the queue's states are, each entry by
244its own method and with the same commit messages, on `g1t-queue/<id>`
245branches that are deleted afterwards, and never land. They
246do not hold up the queue: the entries behind are tested again at once. When
247a combination cannot be built or checked, or its checks are still running
248past the rule's timeout when the queue next moves, g1t stops and reports
249the entries still suspected.
250
251The result identifies a failing combination, not proof of one root cause.
252Missing or pending checks on the original change do not count as passing
253in isolation. Agents should inspect the recorded checks and reproduce the
254failure before choosing a repair.
255
256### Who is told
257
2581. Its pull request records the failed check, saying why: which workflow
259 failed on the state, which required check did not report, or, for a
260 conflict, the pull request ahead it collided with and the files.
2612. Its conversation records that it was taken out of the queue, then what
262 g1t found: the pull requests it fails with, whether that was narrowed
263 down, and the check that failed; or, with nothing ahead, the pull
264 requests that landed on `main` since it branched.
2653. Its issue's conversation says the same in one line, and so does the
266 pull request's [session](/guides/why-blame/#sessions), so the agent
267 working on it reads it there.
2684. A [decision record](/guides/why-blame/#decision-records) of kind
269 `integration_failure`, confidence `tested`, keeps it, with the
270 combination, the check, the suspects, `bisected` and `landed` as
271 evidence. [`get_work`](/guides/sessions/)'s `last_integration_failure`
272 is the latest of these.
2735. The `integration.failed` [event](/guides/webhooks/#events) carries the
274 same to webhooks and to anyone following the repository's events:
275 `pull` (the entry that failed, also `number`), `issue`, `combination`
276 (the pull requests tested together, in order), `check` (`name`,
277 `conclusion`, `url`), `bisected`, `suspects`, `other_pulls`, `landed`
278 (with nothing ahead, the pull requests merged into `main` since it
279 branched, newest first; otherwise empty) and `commit`.
2806. The entries that were tested on top of it are tested again without it.
281
282A combined state that passes is recorded too: an `integration_passed`
283decision and the `integration.passed` event, with `pull` and `combination`.
284
285On the site, the pull request shows the failure above its checks until it
286passes in the queue or merges: the pull requests tested together, with the
287one that answers for it marked, the check that failed (linked to its run),
288what it fails with, and the fix once one is recorded. Its issue shows the
289same under its pull requests, and the repository's
290[Work page](/guides/sessions/#on-the-site) lists it among what happened.
291
292### Getting back in the queue
293
294The merge stays asked for. Push a fix to the pull request, starting from
295`main` as it is now: once every check on the new commit has finished, none
296failed and every required check passed, g1t merges it again as whoever
297queued it, which puts it back in the queue. Everything a merge needs is
298checked again then, so if an approval was dismissed by the push, the
299conversation says so and you merge it again once it is approved. Until a
300new commit's checks pass, merging it again is refused: "It failed in the
301merge queue; push a fix to try again."
302
303A pull request whose checks are not reported by workflows (none run on its
304pushes) is not put back by itself: merge it again once it is fixed.
305
306## The Merge queue page
307
308Every repository has a **Merge queue** page, at
309`g1t.sh/<workspace>/<repo>/queue`, in the repository's sidebar. It
310refreshes on its own while anything is queued.
311
312**In the queue** lists the entries in order, starting from `main`'s commit.
313Each shows:
314
315| | |
316| --- | --- |
317| State | **Waiting**, **Testing** or **Passed**. |
318| Tested as | `main` and the pull requests merged into it, such as `main + #41 + #44`. |
319| Checks | How its state's checks stand, linked to the `merge_group` workflow run on the tested commit (each run by name when there are several). |
320| Who | The person or coding agent who made the pull request, and who queued it. |
321| Commit | The tested state's commit. |
322
323**Recently** lists the last 20 that left the queue: **Landed**, **Failed**
324or **Removed**. A failed entry shows why.
325
326## From the API or an agent
327
328The `pull_request` tool's `merge_queue` action, or
329`GET /repos/{owner}/{name}/queue`, returns the queue.
330It is public for a public repository.
331
332```sh
333curl https://api.g1t.sh/repos/acme/web/queue
334```
335
336```json
337{
338 "enabled": true,
339 "active": [
340 {
341 "number": 44,
342 "title": "Add a --shout flag",
343 "agent": "ada",
344 "state": "testing",
345 "ahead": [41],
346 "base_commit": "8f3c2e1…",
347 "combined_commit": null,
348 "results": [],
349 "enqueued_by": "ada"
350 }
351 ],
352 "recent": []
353}
354```
355
356| Field | |
357| --- | --- |
358| `enabled` | Whether the repository merges through the queue. |
359| `active` | The entries waiting to land, in order. |
360| `recent` | Those that landed or left, newest first. |
361| `state` | `waiting`, `testing`, `passed`, `failed`, `landed` or `removed`. |
362| `ahead` | The pull requests merged ahead of it in the state being tested. Empty when it was tested on `main` alone. |
363| `base_commit` | The commit of `main` the state was built on. |
364| `combined_commit` | The tested state. |
365| `results` | What building the state recorded, each with `command`, `passed` and `output`. The workflow runs on it are on its commit, `combined_commit`. |
366| `error` | Why it failed: a conflict, a workflow that failed on it, a required check that did not report, or what could not be built. |
367| `enqueued_by` | Who added it: a username, or `g1t` when it was merged automatically. |
368
369An agent working on a pull request learns of a failure without polling the
370queue: from the `integration.failed` event, from the note in the pull
371request's session (`pull_request` `read_session`), or from its decisions
372(`pull_request` `list_decisions`, or
373`GET /repos/{owner}/{name}/pulls/{number}/decisions`).