| 1 | --- |
| 2 | title: Merge queue |
| 3 | description: Test each pull request together with the ones ahead of it, so main only moves to a state whose required checks passed. |
| 4 | --- |
| 5 | |
| 6 | Merging one pull request at a time, each caught up with `main`, keeps every |
| 7 | merge clean as text. It does not prove the result works: two changes can |
| 8 | merge without a conflict and still break each other. With the merge queue |
| 9 | on, a pull request is tested together with everything ahead of it before it |
| 10 | lands, and `main` only ever moves to a state whose required checks passed. |
| 11 | |
| 12 | g1t builds the queue's states in the repository itself, without a |
| 13 | sandbox. What checks them is the repository's `merge_group` |
| 14 | [workflows](/guides/actions/), which run and are billed like any other |
| 15 | workflow run. |
| 16 | |
| 17 | ## Turn it on |
| 18 | |
| 19 | 1. Open the project's **Settings → Rules**. You need the Admin |
| 20 | [role](/guides/access-and-roles/) on its repository. |
| 21 | 2. Open the ruleset that covers the default branch, or create one. |
| 22 | 3. 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. |
| 25 | 4. Save. |
| 26 | 5. 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 | |
| 31 | From the API, send `merge_queue` to `PATCH /repos/{owner}/{name}/settings` |
| 32 | (or `update_repo_settings`), which adds the rule to the "Default branch |
| 33 | protection" [ruleset](/guides/rules/): |
| 34 | |
| 35 | ```sh |
| 36 | curl -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 | |
| 44 | Merging a pull request, from its page (**Add to the merge queue**), with |
| 45 | the `pull_request` tool's `merge` action, or with |
| 46 | `POST /repos/{owner}/{name}/pulls/{number}/merge`, |
| 47 | adds it to the queue instead of changing `main`. Everything a merge needs |
| 48 | is still checked first: the pull request must be ready for review, every |
| 49 | [required check](/guides/pull-requests/#required-status-checks) must have |
| 50 | passed on its head, and it must have the approvals the repository asks |
| 51 | for, including its [code owners'](/guides/codeowners/#require-review-from-code-owners) |
| 52 | when it requires them. Only people with the Write [role](/guides/access-and-roles/) or higher can add to the |
| 53 | queue. Merging a pull |
| 54 | request that is already queued changes nothing. |
| 55 | |
| 56 | The pull request's conversation records who added it, and its page shows |
| 57 | where it is in the queue. Anyone who can merge can take it out with **Remove from the |
| 58 | queue**. Closing a pull request also takes it out. |
| 59 | |
| 60 | ## How entries are tested |
| 61 | |
| 62 | g1t takes up to four entries (the rule's `max_entries_to_build`) from the |
| 63 | front of the queue and tests them all at once, speculatively. Each entry's |
| 64 | state 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 | |
| 73 | Each entry is landed on the state ahead of it by its own |
| 74 | [merge method](/guides/pull-requests/#merge-methods), exactly as merging it |
| 75 | there 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 | |
| 86 | The method is the one asked for when it was merged into the queue (the |
| 87 | merge box's choice, `merge_method` in the API), else the merge queue |
| 88 | rule's `merge_method` (merge commit unless set). Both must be methods the |
| 89 | repository and the branch's [rules](/guides/rules/) allow, or merging is |
| 90 | refused, saying which are; linear history rules out merge commits. |
| 91 | A commit message written then (`commit_title`, `commit_message`) is kept |
| 92 | with the entry; otherwise the repository's default message is used, worked |
| 93 | out when the entry is first built. Sign-off is added where the repository |
| 94 | requires it. The same limits apply as to merging directly: g1t makes at |
| 95 | most 1,000 changed files, 100 files merged line by line and 250 rebased |
| 96 | commits itself. |
| 97 | |
| 98 | If every entry passes, the four can land one after another without being |
| 99 | tested again. The next batch starts when nothing is being tested. A batch |
| 100 | that takes longer than 45 minutes (`check_response_timeout_minutes`) is tested again. |
| 101 | |
| 102 | ### What each state is held to |
| 103 | |
| 104 | g1t pushes each state it built to a branch of its own, `g1t-queue/<entry>-<attempt>`, |
| 105 | and 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 | |
| 112 | So a change that breaks something another change ahead of it relies on is |
| 113 | caught here, even when it merges without a conflict and its own checks |
| 114 | passed. The branch is deleted once the entry lands or leaves the queue. |
| 115 | |
| 116 | A workflow opts in like this: |
| 117 | |
| 118 | ```yaml |
| 119 | on: |
| 120 | pull_request: |
| 121 | merge_group: |
| 122 | ``` |
| 123 | |
| 124 | Required 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 |
| 127 | check CI cannot report on it: no workflow runs on merge_group events. Add |
| 128 | merge_group to the on: of the workflows the branch requires". A required |
| 129 | check that a workflow did not report on the state fails it the same way. |
| 130 | A repository that requires no checks and has no `merge_group` workflows |
| 131 | only has each state built: an entry passes once it merges cleanly with |
| 132 | what is ahead of it. |
| 133 | |
| 134 | Each build has its own attempt token. A result from an old attempt cannot |
| 135 | replace a retry or put a removed entry back in the queue. Workflow results |
| 136 | apply only to the combined commit still being checked. |
| 137 | Retries use separate Git branches, so a late build cannot overwrite the |
| 138 | new attempt's branch. Workflow results wait until the whole group has been |
| 139 | registered; a dispatch error does not count as a pass. |
| 140 | |
| 141 | ## How entries land |
| 142 | |
| 143 | Entries land in order. When an entry has passed and everything ahead of it |
| 144 | has landed, `main` moves to exactly the state that was tested: since each |
| 145 | state was built on the one ahead of it, that is a fast-forward to the |
| 146 | commits its method made, which are the commits its checks ran on. The pull |
| 147 | request is recorded as merged by its method, with the commit `main` moved |
| 148 | to as its merge commit. The issue closes and the other pull requests for |
| 149 | it are superseded, as with any merge. |
| 150 | |
| 151 | Current reviews and branch rules are checked again before publication, |
| 152 | against the combined commit's checks and the entry's merge method. Once publication starts, removal, |
| 153 | closing or conversion to draft returns a conflict until it finishes. If the |
| 154 | service stops after updating Git, the next queue poll resumes bookkeeping |
| 155 | for that same commit instead of publishing a different branch tip. |
| 156 | |
| 157 | Before 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 | |
| 164 | A pull request that is already known to conflict with `main` is not added |
| 165 | to the queue: [its merge box](/guides/pull-requests/#conflicts) says which |
| 166 | files conflict and how to resolve them first. One that is only behind `main` |
| 167 | does not need to catch up to join the queue, since the queue tests it on |
| 168 | top of `main`. Where the repository requires pull requests to be up to |
| 169 | date, [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 | |
| 174 | An entry fails when its `merge_group` workflows or required checks fail |
| 175 | on the combined state, when its change clashes with what is ahead of it, or |
| 176 | when the state cannot be built (a fast-forward that does not contain what |
| 177 | is ahead, or more than g1t makes itself). One that cannot be built leaves |
| 178 | the queue, and the entries behind it are built in the next batch without |
| 179 | it. |
| 180 | |
| 181 | ### Who answers for it |
| 182 | |
| 183 | A state that fails while an entry ahead of it is still being tested is |
| 184 | held until those ahead are settled. If one of them fails too, the held |
| 185 | entry is tested again without it and is not blamed. Once every entry ahead |
| 186 | has passed, the held entry answers for the failure. So when #41 is |
| 187 | broken, it leaves the queue, and #44 and #46, which were tested with #41 |
| 188 | in them, are tested again without it rather than taken out with it. |
| 189 | |
| 190 | The entry that answers for a failure leaves the queue, and g1t works out |
| 191 | what 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 | |
| 205 | An entry with nothing ahead of it is tested on `main` as it is now. When |
| 206 | its 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 |
| 208 | what it now breaks with: #1 changed a function's signature, say, and this |
| 209 | pull request, written before #1 landed, still calls the old one. |
| 210 | |
| 211 | The entry still answers for the failure, since it is the one that has to |
| 212 | adapt, and nothing extra is built. g1t looks at the 50 pull requests most |
| 213 | recently merged into `main` before the entry was tested, keeps those whose |
| 214 | commits the entry's history (its latest 1,000 commits) does not hold, and |
| 215 | reports up to ten of them, newest first, as `landed`. A pull request that |
| 216 | was brought up to date with `main` since it branched holds what it merged, |
| 217 | so only what landed after that is named. The note on the pull request then |
| 218 | reads, 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 | |
| 227 | When only one pull request landed it is named as the likely cause; when |
| 228 | several did, they are listed as what it may conflict with. When none is |
| 229 | found (`main` moved by a push rather than a pull request, say), the note |
| 230 | says the change is the cause, as before. A conflict, a required check that |
| 231 | did not report and a state that could not be built never look this up. |
| 232 | |
| 233 | ### Narrowing it down |
| 234 | |
| 235 | When an entry passes its own checks but fails combined with several entries |
| 236 | ahead, g1t tests smaller combinations using the same `merge_group` workflows. |
| 237 | A failing subset becomes the new suspect set. A passing subset causes g1t |
| 238 | to test its complement before excluding anything. If both subsets pass, |
| 239 | g1t retains the last failing combination: the failure may involve several |
| 240 | changes together, or may not be deterministic. Diagnosis stops after three |
| 241 | extra builds, retaining the last combination demonstrated to fail. |
| 242 | |
| 243 | These combinations are built as the queue's states are, each entry by |
| 244 | its own method and with the same commit messages, on `g1t-queue/<id>` |
| 245 | branches that are deleted afterwards, and never land. They |
| 246 | do not hold up the queue: the entries behind are tested again at once. When |
| 247 | a combination cannot be built or checked, or its checks are still running |
| 248 | past the rule's timeout when the queue next moves, g1t stops and reports |
| 249 | the entries still suspected. |
| 250 | |
| 251 | The result identifies a failing combination, not proof of one root cause. |
| 252 | Missing or pending checks on the original change do not count as passing |
| 253 | in isolation. Agents should inspect the recorded checks and reproduce the |
| 254 | failure before choosing a repair. |
| 255 | |
| 256 | ### Who is told |
| 257 | |
| 258 | 1. 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. |
| 261 | 2. 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. |
| 265 | 3. 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. |
| 268 | 4. 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. |
| 273 | 5. 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`. |
| 280 | 6. The entries that were tested on top of it are tested again without it. |
| 281 | |
| 282 | A combined state that passes is recorded too: an `integration_passed` |
| 283 | decision and the `integration.passed` event, with `pull` and `combination`. |
| 284 | |
| 285 | On the site, the pull request shows the failure above its checks until it |
| 286 | passes in the queue or merges: the pull requests tested together, with the |
| 287 | one that answers for it marked, the check that failed (linked to its run), |
| 288 | what it fails with, and the fix once one is recorded. Its issue shows the |
| 289 | same 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 | |
| 294 | The 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 |
| 296 | failed and every required check passed, g1t merges it again as whoever |
| 297 | queued it, which puts it back in the queue. Everything a merge needs is |
| 298 | checked again then, so if an approval was dismissed by the push, the |
| 299 | conversation says so and you merge it again once it is approved. Until a |
| 300 | new commit's checks pass, merging it again is refused: "It failed in the |
| 301 | merge queue; push a fix to try again." |
| 302 | |
| 303 | A pull request whose checks are not reported by workflows (none run on its |
| 304 | pushes) is not put back by itself: merge it again once it is fixed. |
| 305 | |
| 306 | ## The Merge queue page |
| 307 | |
| 308 | Every repository has a **Merge queue** page, at |
| 309 | `g1t.sh/<workspace>/<repo>/queue`, in the repository's sidebar. It |
| 310 | refreshes on its own while anything is queued. |
| 311 | |
| 312 | **In the queue** lists the entries in order, starting from `main`'s commit. |
| 313 | Each 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** |
| 324 | or **Removed**. A failed entry shows why. |
| 325 | |
| 326 | ## From the API or an agent |
| 327 | |
| 328 | The `pull_request` tool's `merge_queue` action, or |
| 329 | `GET /repos/{owner}/{name}/queue`, returns the queue. |
| 330 | It is public for a public repository. |
| 331 | |
| 332 | ```sh |
| 333 | curl 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 | |
| 369 | An agent working on a pull request learns of a failure without polling the |
| 370 | queue: from the `integration.failed` event, from the note in the pull |
| 371 | request's session (`pull_request` `read_session`), or from its decisions |
| 372 | (`pull_request` `list_decisions`, or |
| 373 | `GET /repos/{owner}/{name}/pulls/{number}/decisions`). |