| 1 | --- |
| 2 | title: Work and the merge queue |
| 3 | description: How the work service keeps issues, pull requests, checks and rulesets, coordinates sessions and claims, decides and lands a merge, and tests many changes at once in the merge queue. |
| 4 | --- |
| 5 | |
| 6 | import Aside from '../../../components/Aside.astro'; |
| 7 | import Diagram from '../../../components/Diagram.astro'; |
| 8 | |
| 9 | `work` (`services/work`, Rust) keeps everything about a change that is not |
| 10 | the code itself: issues, pull requests, comments, reviews, labels, |
| 11 | milestones, rulesets, checks and commit statuses, CODEOWNERS, and the |
| 12 | merge queue. Its database is `g1t-work`. The code lives in `repos`, and |
| 13 | `work` asks `repos` whenever it needs it. |
| 14 | |
| 15 | ## Issues and pull requests |
| 16 | |
| 17 | Issues and pull requests share one sequence of numbers per repository |
| 18 | (the `counters` table), so `#12` names exactly one of them. A pull request |
| 19 | records where its head is: a fork of its own, `pulls--<id>` in the git |
| 20 | store, or a branch in the repository ([Git](/architecture/git/#pull-request-forks)). |
| 21 | |
| 22 | `work` reads every push from its event queue. A push to a pull request's |
| 23 | head updates it: the new head, the files it changes, whether it is behind |
| 24 | the base, and which other open pull requests change the same files, the |
| 25 | overlap shown on each. |
| 26 | |
| 27 | When a pull request merges, its issue closes and records which pull |
| 28 | request resolved it, and the issue's other open pull requests close as |
| 29 | superseded ([How g1t works](/concepts/overview/#several-pull-requests-for-one-issue)). |
| 30 | |
| 31 | ## Coordination |
| 32 | |
| 33 | Many agents and people can work on one repository at once. `work` keeps |
| 34 | what lets them see each other, around the issue, which is the unit of |
| 35 | work (`services/work/src/coordination/`): |
| 36 | |
| 37 | | Table | Holds | Module | |
| 38 | | --- | --- | --- | |
| 39 | | `sessions` | One run of someone's own harness (`claude_code`, `codex`, `cursor`, `copilot` or `other`), attached to an issue, a pull request and a branch | `sessions.rs` | |
| 40 | | `session_entries` | What a session recorded: prompts, reasoning, tool calls, notes, each with an idempotency `key` | `sessions.rs` | |
| 41 | | `claims` | An advisory, expiring "I'm working on #12" | `claims.rs` | |
| 42 | | Issues' `blocked_by` | The issues that have to land first, refused if it would make a cycle | `dependencies.rs` | |
| 43 | | `overlap_pairs` | Pairs of open pull requests changing the same files, announced once per pair | `overlaps.rs` | |
| 44 | |
| 45 | <Diagram name="coordination" /> |
| 46 | |
| 47 | Three choices shape it: |
| 48 | |
| 49 | - **Nothing is a lock.** A claim is advice: anyone may claim an issue |
| 50 | someone else holds, is told about them, and decides. Nothing is ever |
| 51 | refused because of a claim (step 9 in the diagram). |
| 52 | - **Nothing waits on a harness.** No cron sweeps for quiet sessions or |
| 53 | stale claims. The next read or change of a repository's coordination |
| 54 | finds them (`sweep`) and announces them then: a session quiet for 15 |
| 55 | minutes is idle, one quiet for 2 hours has ended, and a claim not renewed |
| 56 | within its time (30 minutes unless asked, at most 240) has expired. |
| 57 | - **Everything can be sent twice.** Starting a session is idempotent on the |
| 58 | harness's own session id, and each entry on its `key`, so a hook can call |
| 59 | every time without checking first, and a retry after a lost answer does |
| 60 | no harm. |
| 61 | |
| 62 | `get_work` gathers an issue's whole context in one call (`context.rs`): the |
| 63 | issue, its definition of done, the required checks, claims, sessions, |
| 64 | pull requests with their checks, overlaps and place in the queue, what it |
| 65 | is blocked by and blocks, its decision records, and its latest |
| 66 | coordination events. A session's entries on a pull request are what |
| 67 | **Blame** reads to show the agent's account of a line ([Sessions and |
| 68 | why-blame](/guides/why-blame/)). [Sessions, claims and |
| 69 | dependencies](/guides/sessions/) has the calls. |
| 70 | |
| 71 | ## Checks and statuses |
| 72 | |
| 73 | Anything can report on a commit: a workflow, an integration, g1t itself. |
| 74 | Each report is a status with a `context` (its name) and a state, kept per |
| 75 | commit in `commit_statuses`; richer reports are check runs and suites. |
| 76 | |
| 77 | Actions reports one status per run, named `<workflow> / <event>`, such as |
| 78 | `CI / pull_request` (`services/actions/src/plan.rs`). A required check |
| 79 | named `CI` is met by a status of that name for any event: |
| 80 | `check_name` in `crates/contracts/src/work.rs` reads the name before the |
| 81 | last ` / `. One that failed fails the check, even if another passed. |
| 82 | |
| 83 | ## Deciding a merge |
| 84 | |
| 85 | Rulesets are stored in `work`, and judged by `crates/rules`, which does no |
| 86 | I/O and so can be tested in full. The same rules run in two places: |
| 87 | |
| 88 | | When | Who asks | What is judged | |
| 89 | | --- | --- | --- | |
| 90 | | A push | `repos`, before anything is stored | Which refs may move, and how: deletion, force-push, signed commits, file paths | |
| 91 | | A merge | `work` | Required reviews, required checks, CODEOWNERS, up to date, the merge queue | |
| 92 | |
| 93 | `requirements` gathers every rule that applies to the target branch, and |
| 94 | `judge` returns, for each, whether it passes, fails or is still waiting, |
| 95 | and whether the person asking may bypass it. A merge happens only when |
| 96 | none fails or waits, for people and coding agents alike. |
| 97 | |
| 98 | When everything passes, `work` asks `repos` to land the pull request's |
| 99 | head on the base, and publishes `pull.merged`. |
| 100 | |
| 101 | ## The merge queue |
| 102 | |
| 103 | Two changes can merge without a conflict and still break each other. A |
| 104 | repository whose rules require the merge queue tests each pull request |
| 105 | together with the ones ahead of it, and moves `main` only to a state whose |
| 106 | required checks passed (`services/work/src/queue.rs`). |
| 107 | |
| 108 | <Diagram name="merge-queue" /> |
| 109 | |
| 110 | 1. **Build.** Up to `max_entries_to_build` entries (4 by default) are |
| 111 | tested at once. For each, a sandbox merges the default branch with that |
| 112 | entry and every entry ahead of it, and pushes the result to |
| 113 | `g1t-queue/<entry>` in the repository. |
| 114 | 2. **Test.** Workflows that run on `merge_group` run on each of those |
| 115 | commits, and the required checks have to pass there. |
| 116 | 3. **Land.** Entries land in order. Once everything ahead of it has landed, |
| 117 | an entry moves the default branch to the very commit that was tested. |
| 118 | 4. **Eject.** An entry whose checks fail leaves the queue with a failed |
| 119 | check run on its pull request, and the entries behind it are tested |
| 120 | again without it. |
| 121 | |
| 122 | | State | Means | |
| 123 | | --- | --- | |
| 124 | | `waiting` | In the queue, not yet being built | |
| 125 | | `testing` | Built, its checks running | |
| 126 | | `passed` | Its checks passed; it lands when those ahead have | |
| 127 | | `failed` | Its checks failed; it leaves the queue | |
| 128 | | `landed` | On the default branch | |
| 129 | | `removed` | Taken out by hand | |
| 130 | |
| 131 | ### Who answers for a failure |
| 132 | |
| 133 | A combined state can fail because of the entry itself, or because of an |
| 134 | entry ahead of it. `integration.rs` works out which: |
| 135 | |
| 136 | - **Held, not blamed.** A state that fails while an entry ahead is still |
| 137 | being tested is held. If that entry fails too, the held one is tested |
| 138 | again without it and is not blamed. Only once everything ahead has |
| 139 | passed does the held entry answer for the failure, so a broken change is |
| 140 | blamed on itself, never on what was queued behind it. |
| 141 | - **Narrowed down.** An entry whose own checks pass but whose combination |
| 142 | fails breaks *with* some of the entries ahead. `work` builds the default |
| 143 | branch with half of them and the entry, and halves again, within three |
| 144 | extra builds (`PROBE_BUDGET`), recorded in `queue_probes`. The builds run |
| 145 | on `merge_group` like any queued state and never land. What is left is |
| 146 | reported as the `suspects`. |
| 147 | - **Told everywhere.** The answer goes on the pull request's and the |
| 148 | issue's timelines, into the pull request's session, as a decision record |
| 149 | (`integration_failure`, confidence `tested`, in `decisions`), and as the |
| 150 | `integration.failed` event. A passing combination is recorded as |
| 151 | `integration.passed`. |
| 152 | - **Rejoining.** A pull request the queue took out keeps its merge |
| 153 | request. Once a new commit's checks pass, it is merged again, into the |
| 154 | queue, as whoever queued it, by the rules as they are then. |
| 155 | |
| 156 | [The merge queue](/guides/merge-queue/#who-answers-for-it) has the table of |
| 157 | outcomes from a person's side. |
| 158 | |
| 159 | <Aside title="How states are built"> |
| 160 | `work` builds each candidate in the repository itself, through repos' |
| 161 | `build_queue_step`: the entry landed by its own merge method on the |
| 162 | state ahead of it, with the commits `land` would make there, pushed to |
| 163 | the attempt's `g1t-queue/` branch. Landing is then a fast-forward to |
| 164 | exactly that commit, and no sandbox is involved. |
| 165 | [The merge queue](/guides/merge-queue/) has the settings. |
| 166 | </Aside> |
| 167 | |
| 168 | ## Events it publishes and reads |
| 169 | |
| 170 | `work` publishes what happens to issues, pull requests, comments, checks |
| 171 | and the queue (`issue.*`, `pull.*`, `comment.*`, `status.created`, |
| 172 | `queue.changed` and the like), and the coordination events: |
| 173 | `session.started`, `session.ended`, `work.claimed`, `work.released`, |
| 174 | `work.claim_expired`, `work.handoff_requested`, `dependency.changed`, |
| 175 | `overlap.detected`, `integration.failed`, `integration.passed` and |
| 176 | `decision.recorded`. It reads |
| 177 | `git.push` and a few repository events from `g1t-events-work` |
| 178 | ([Events](/architecture/events/#subscriber-queues)). |