A research brief with diagrams.
Session 002 · 2026-10-03 · Proposed — pending Fede's picks
Contents
Session 002 · 2026-10-05 night · M = measured, E = estimate
Fede's ask (2026-10-05): minutes and $ per day when optimization started, after tonight, and where the next 50% is. Projected vs actual: the actual column is refilled each morning from real runs.
Minutes are machine-minutes from the runner job records. Dollars are runner compute only, from AWS billing; add about $47/day of fixed cost (dashboard estimate) for the all-in number. Days are UTC.
| Per weekday | |
|---|---|
| Start (Fri Oct 2), minutes | 299k (M) |
| Start (Fri Oct 2), $ | $609 (M) |
| After tonight, minutes | 164k–174k (E); Mon Oct 5 was 181k (M) |
| After tonight, $ | $110–150 (E), +$30–90 if the merge-queue checks stay on full-price machines |
Oct 2 was the first full weekday on the new runner fleet, and it was an incident day: 1,522 of 2,232 merge-queue runs went red, and $352 went to full-price machines. Most of the drop to about $190 on Oct 5 (E; $134 billed so far) came before tonight: cheap spare-capacity machines everywhere, the disk fix, and the guard tests moved out of the queue gate. Both days are weekdays, so no scaling is needed.
| Item | Projected (E) | Actual so far (M) | Still pending |
|---|---|---|---|
| Runner $/day | $188 → $85–100 | Needs a full day; our own estimate is $110–150 | Oct 6 billing |
| Monthly | $5,600 → $2,600–3,000 | — | Same |
| Merge-queue checks job | 10.6 → ~6 min | Green runs 13.2 min on Oct 5 (565 jobs) → 14.8 min after tonight's change (15 jobs); still 8-core, now full-price | 16-core change |
| PR open → live | 2.5 h → 1.5 h | — | Dashboard, after merges |
| Peak queue throughput | ~2× | Peak 22 merges/h on Oct 5; 23/h at 01Z Oct 6 | After merges |
| Guard tests run once per train | −$23 to −33/day | Guard jobs per queue run 6.0 → 4.0 (first 30 min, early) | Full day |
| No full-price machines | −$25/day | Opposite so far: full-price pace ~$12 → ~$100/day in the hour after tonight's checks change | Checks back to cheap machines |
| Full set on 8 × 8-core | −$13/day | One trial run | PR not merged |
| Guard tests on 3 × 8-core | −$5 to −11/day | — | PR not merged |
| Type check beside the build | +$5 to +10/day | — | Open, Fede's call |
| Oct 2 | Oct 5 | |
|---|---|---|
| Red runs | 1,522 of 2,232 | 238 of 793 |
| Their minutes | 112k | 18.4k |
| Their $ (E) | ~$327 | $26–45; dashboard ≈$44 plus 37 PR-hours lost |
| Red trains that only echoed another PR's break | — | 2 of 3 (M) |
| Merge queue's share of all minutes | 58% | 37% |
Sources: CI cost ledger (dollars); runner job tables, 1.29M jobs since Sep 29; the bigger-box, red-trains and always-run audits; the CI factory and CI/CD architecture pages; GitHub PR states at 03:15Z Oct 6. All session 002.
Session 002 · 2026-10-05 night · four read-only audits of GitHub data (Sep 5 → Oct 5) · every number carries the audit's own label: measured or estimate
One line: the merge queue's full suite mostly catches a PR's own tests, which never ran on the PR because a 200-file cap switched them off. Remove the cap, run the real selection on the PR, and most breaks get caught before the queue.
From the raw transcript (Fede and Gera, Oct 5): Gera's first priority is “never have main red,” because “things that are supposed to break need to break in your PR,” and one head failure “took down 58 of the previous PRs, roughly $10.” He will only cut tests “once 90% of the time we're never failing merge queue.” Both agreed the fix is a smarter blast-radius algorithm, “rather than just what files did you touch,” with “something at the merge too.” Fede's target: “speedy and low cost,” a CI budget of about $2,000 a month. Gera's pain: about three hours from PR open to live.
| Question | Short answer | Where |
|---|---|---|
| Back-end tests for a front-end change? | No, already not. Front-end-only PRs run a median of 8 test files (measured, 122 sampled). | 2b |
| Why so many red trains? | Big PRs run none of their own tests until the queue, so the queue is where they fail. | 2b, 2c |
| Can the queue drop the full suite (nightly only)? | No. It catches ~14 real breaks a day nothing else catches. | 2a |
For 33.5 hours (Oct 2 10:34 PM UTC → Oct 4 8:05 AM UTC) every train ran the full suite. In that window 20 PRs broke a test that the affected tests, the guards and the type checks all passed: about 14 a day (measured). Over the whole 72 hours it was 39 to 48 PRs, 13 to 16 a day. “Real” means the PR pushed a fix before merging. With the full suite nightly only, each would have merged and turned main red, so nightly-only was rejected.
| Queue runs (trains), measured | Full suite on every train (33.5 h) | Full suite on big trains only (29.9 h) | Whole 72 h |
|---|---|---|---|
| Trains | 1,194 (691 green) | 852 (556 green) | 2,132 |
| Failed only in the full suite | 211 | 155 | 366 |
| PRs with a real break only the full suite caught | 20 | 19 confirmed to 28 | 39 to 48 |
| Innocent PRs thrown out with them | 8 | 27 | 35 |
| Flake or no cause found | 5 | 3 | 8 |
Live examples: #15837 broke four prospect-list tests (run); #16231 broke three tour tests (run); #16659 broke the first-email compose test (run). One got through: #15779 merged unchanged and the after-merge lane traced main red to it (#15926).
When a PR's change selects more than 200 test files, the PR runs zero of its own unit tests and the check goes green as “Full suite deferred to main” (measured, from the code). That happened to 666 of 1,430 sampled PRs, 46.6% (measured, 14 days). The suite grew from 5,736 to 8,277 files in those 14 days, so 200 is now 2.4% of it. Of the 48 real queue catches above, 47 came from capped PRs (estimate) and 34 broke a test file the PR itself edited (measured). The queue is catching tests the PR should have run.
| Layer the PR changed | PRs, 14 days | Over the cap (sampled, measured) | Median tests selected (measured) |
|---|---|---|---|
| Front end | 415 | 4 of 122 (3%) | 8 |
| Back end | 1,491 | 308 of 461 (67%) | 854 |
| Shared / hub file | 431 | 153 of 154 (99%) | 4,035 |
| Mixed | 1,355 | 199 of 408 (49%) | 125 |
| Scripts / CI only | 587 | 0 of 175 | 1 |
| Tests only | 349 | 0 of 101 | 1 |
| All | 4,692 | 666 of 1,430 (46.6%) | 54 |
| Capped PRs | File | Tests it selects alone | Live example |
|---|---|---|---|
| 287 | shared data types (types.ts) | 4,721 | #10666: 3,205 (job) |
| 153 | any package.json (forces everything) | all | #14138: 7,320 (job) |
| 142 | Clara conversation manager (in the loop) | 1,419 | #11225: 478 (job) |
| 118 | generated route list | 788 | #11074: 530 (job) |
| 116 | Clara leasing tools (in the loop) | 1,419 | #12647: 808 (job) |
| 108 | data-layer front door (data/index.ts) | 3,941 | #13102: 3,551 (job) |
| 97 | JSON repository | 3,941 | #11747: 3,529 (job) |
| 85 | portfolio registry keys | 3,130 | #10960: 887 (job) |
| 72 | data store | 3,941 | #13843: 3,765 (job) |
| 65 | guest-card first-email compose (in the loop) | 1,419 | #10855: 469 (job) |
Worked examples, today vs no cap: a toast-message change, #16066, selected 312 tests and ran 0 on the PR (job); a data-store change, #15724, selected 4,051 and ran 0 (job); a small renewal-offers screen, #16237, ran its 1 test (job). “In the loop” = part of the 218-file import loop (section 4).
Each cut made sense alone. Stacked, they mean a big PR gets no unit tests until the queue, so the queue is where it fails, and every train behind it is thrown away.
| When | Cut | What a big PR ran after it |
|---|---|---|
| Jul 14 | #3732 caps runaway “affected” selections at 200 files (from repo history) | Full suite in the queue instead of its selection |
| Sep 18 | #9480 full suite once per review round on big PRs | Full suite on the PR, once per review round |
| Sep 30 | #14402 full unit shards on main only, not on PR verdicts | Zero of its own unit tests on the PR; full suite only in the queue and after merge |
Main was red about 145 minutes a day through September, doubled the last week, and is at 154 a day now that a lane after merge runs the full suite and reverts (estimate, commit-time method). Machines failing to start, the live voice-platform tests and a test-runner timeout are fixed. Real regressions, guard tests, queue cascades and the after-merge lane dying are still open.
| Breaking point | Red-main episodes (measured) | Red min (estimate) | Status | Example |
|---|---|---|---|---|
| Real regressions that pass PR checks | 46 + 27 after-merge | ~1,940 + 1,653 | open by design ~1.3/day | #16246 |
| Guard / drift / inventory tests | 56 + 7 | ~1,930 + 370 | partly fixed | #16405 |
| Machines failing to start | 42 | ~930 | fixed none since Sep 30 | run |
| Live voice-platform tests | 41 | ~620 | fixed deleted (#15270) | run |
| Production deploy promote failed | 8 | ~400 | open, low rate | run |
| After-merge lane dies, no test to blame | 7 | 214 (measured) | open, rising Gera draft #16854 | #16847 |
| Queue cascades (innocent PRs thrown out) | 818 cancelled trains, Oct 3–5 | 8,564 wall-min (measured) | open Gera #16802 | #16581 |
| Test-runner worker timeout on green shards | 9–12 | ~110 | fixed | run |
| Voice-harness gate test | 5 | ~145 | fixed | run |
| Type check / lint on main | 12 | ~205 | fixed | run |
Gera's lane mostly made the machinery tell the truth (review verdicts, guard results, runner deaths no longer read as code failures). Fede's lane mostly changed shape and cost (merge queue, after-merge lane, one run per change). Both worked the queue cascade.
| Class | Gera | Fede lane |
|---|---|---|
| Review loop | 61 | 20 |
| Guards, ratchets, pins | 53 | 17 |
| Runners, infra, deploy | 50 | 62 |
| Cascade (queue, freeze, auto-merge, auto-revert) | 48 | 49 |
| Selection (which tests run where) | 33 | 25 |
| Flake | 5 | 7 |
| Cost | 3 | 65 |
| Total | 253 | 245 |
Every queue run (train), Sep 30 10:40 PM UTC → Oct 5 11:34 PM UTC: 2,550 of 4,828 trains were not green (measured). “Now” = since the last fixes went live, Oct 4 4:42 PM UTC (877 trains in 30.9 h), scaled to a day. An echo is a train that failed on the same file as a different PR's train still running ahead of it, so it carried someone else's break (84% of echo PRs later went green with no new commit, measured). And since the queue stops a run the moment any job goes red, “cancelled” almost always means red: only 131 of the 2,550 (5%) were cancelled with nothing red (measured).
| Cause (measured) | 5 days | Now, per day | Live example |
|---|---|---|---|
| Echo of a broken PR ahead, full suite | 696 | 106 | #16836 behind #16733 (run) |
| Echo, guard tests | 917 | 24 | #16750 behind #16745 (run) |
| Echo, type check and affected tests | 107 | 17 | #16387 behind #16321 (run) |
| The PR's own change (a real catch) | 115 | 27 | #16836 broke the judgment-feed test (run) |
| Culprit put back in the queue unchanged | 110 | #16836 again, 10 min later (run) | |
| Corrected Oct 5, 10:40 PM: a re-check of the 20 post-Oct-4 trains found 15 a day were misclassified (the PR had pushed a new commit, so the rule correctly let it back in) and about 1 a day slipped through a known fail-open (#16506, head time could not be read). The rule is already on main (#15897, #16475) and saves about 31 red trains a day (measured). | |||
| Spot machine reclaimed by AWS | 38 | 12 | #16787 (run) |
| Cancelled with nothing red (cascade or PR left the queue) | 131 | 12 | #16356 (run) |
| Repeat of a break whose PR had already left the queue | 49 | 11 | #16742 after #16733 (run) |
| Flake, or fixed on main meanwhile | 31 | 4 | #16807 (run) |
| Test runner crashed, no test failed | 18 | 1 | #16785 (run) |
| Type-check machine disk full | 298 | 0 | #15428 (run) |
| Type-check machine died (out of memory, inference) | 40 | 0 | #16356 (run) |
| Total not green | 2,550 | 230 | no train timed out |
Now, 2 of every 3 red trains are echoes. Each real catch leaves about 5 echo trains behind it (measured, counting echoes behind echoes).
What the fixes so far did (measured, per 100 trains before vs after each merge, because daily volume moved 3–4×). Three worked:
| Fix | What it counts | Before → after, per day | Per 100 trains |
|---|---|---|---|
| #15452 bigger disk on the type-check machine | Disk-full reds | 196.5 → 0 | 18.4 → 0 |
| #16389 type check after build | Type-check machine deaths | 10.7 → 0 | 1.0 → 0 |
| #15860 bookkeeping guards leave the queue gate | Trains thrown out by guard tests | 351.8 → 33.7 | 29.1 → 5.3 |
| Change | What it counts | Per 100 trains, before → after | Why |
|---|---|---|---|
| #15399 full suite back in the queue | Full-suite echoes | 4.5 → 18.4 | went up it catches real breaks, and each one fans out |
| #16325 / #16400 thin queue (full suite on big trains only) | Full-suite reds | 15.8 → 21.7 | no gain per train |
| #15881 queue jobs on spot machines, retry on reclaim | Spot-reclaim reds | 0.5 → 1.5 | went up 3× |
| #15203 auto-quarantine flaky tests | Flake trains | 0.6 → 0.7 | no change |
Two rules against re-queueing an unchanged PR only half worked: #15897 took re-entries from 2.6 to 1.7 per 100 trains, #16475 from 2.4 to 1.6 (20 h of data). Both still open: #16581 (don't throw out bystanders) and #16592 (a spot kill is not a code failure).
What would cut it further. Three measured fixes together remove 59 red trains a day. Corrected Oct 5: the re-queue rule is already live, so the two remaining measured fixes (bookkeeping guards 19, spot as infra 12) remove up to 31 a day, before removing overlap. Shifting real catches onto the PR adds an estimated 68. Together that takes today's ~230 a day to about 100 (estimate); what is left is mostly echoes behind echoes and repeats with no single owner.
| Fix | Red trains a day saved | Label |
|---|---|---|
| ~1 | measured (corrected Oct 5) | |
| Take the last three bookkeeping guards out of the queue's full suite (they run after merge instead) | 19 | measured |
| Treat a spot reclaim as infra and re-queue it (#16592); on-demand machines for the queue's short jobs | 12 | measured |
| Those two together (was 59 with the re-queue rule), overlap not recomputed | up to 31 | measured |
| Run the PR's real test selection before it enters the queue (move 2 below) | ~68 | estimate, if perfect |
| Don't throw out bystanders (#16581) | 0 | measured. |
| All of the above | ~230 → ~100 left | estimate |
Flakes are rare: at most 31 trains in 5 days (an upper bound, since “fixed on main” looks the same). Two tests tracked as flaky, #15851 and #15852, failed here mostly as echoes behind real breaks (22 of 24 and 9 of 10 trains, measured). Source: red-trains audit, session 002, Oct 5.
Fede said “go ahead” on moves 1–4 and “go refactor” on move 7 tonight. Moves 1–4 are fixes to how CI already works, so they ship with no switch.
| Move | Daily cost today | Expected effect | Risk | Status |
|---|---|---|---|---|
| 1. Full-suite machines skip before they boot on normal trains | ~348 normal trains a day still boot two 64-core machines that exit in ~46 s (measured sample ×4) | 9–12 machine-hours a day, ~$10–15 (estimate, not checked against the bill) | Low; a missing signal now runs the suite instead of skipping it | approved building tonight |
| 2. Remove the cap; the PR runs its real selection, split across machines | 47% of PRs run zero own tests; 47 of 48 real queue catches were those PRs | −67% test files run in the queue; 43 of 48 caught on the PR (estimate) | Big PRs select 1,353 files at the median, 4,766 at p90 (measured), so needs splitting | approved building tonight; builds on Gera's #16060 (runs a capped PR's own tests and direct importers), which is a partial step |
| 3. File-reading tests declare their inputs | 5 of 48 real breaks were tests the import graph cannot see | 48 of 48 with move 2 (estimate) | Low | approved building tonight; #5549 |
| 4. Skip cache keyed on content, not on the moving base | 1 of 141 PR runs skipped since #15856; 74 of 215 jobs (34%) re-ran on the same commit (measured) | Up to 34% of those jobs skippable (upper bound, estimate) | A missed input gives a stale pass; needs move 3 first | approved building tonight |
| 5. Queue cascades: cancel orphaned trains (a train no longer in the queue is cancelled as “superseded”) (corrected Oct 5: was “don't throw out ~122 bystanders”; 58 PRs removed in 31 h, only 6 after an echo train, all 6 real culprits) | 299 of 877 trains rebuilt mid-run, about 1,700 run-minutes a day discarded, 110 of them counted red (measured) | About 1,700 → under 100 discarded run-minutes a day; about 85 → 0 orphan trains counted red (estimates) | Low | building tonight #16581 to be closed with the timeline evidence; #16802 (spot as infra) stays the fix for real wrong ejections: spot 10, guard flake 3, runner crash 1 in 31 h |
| 6. Cut the ~940 always-run tests by layer | 941 test files on every normal PR, whatever it changed (measured from config); daily cost not measured | A front-end PR runs near its own 8 instead of ~949 | A cross-layer guard stops running on that PR (still runs in the queue and after merge) | planned, not yet approved |
| 7. Import-structure refactor (218-file loop, five catch-all data files) | 290 PRs in 14 days were over the cap only because of the loop (estimate) | Far fewer PRs over any cap | Wide change across leasing and Clara code | approved architecture; plan in progress |
| 8. Remaining bookkeeping guards out of the queue's full suite (three left) | 24 trains in 30.9 h (measured) | 19 red trains a day saved (measured) | A bookkeeping slip shows up after merge instead of in the queue | planned, not yet approved |
Acceptance bar from the team meeting: queue green rate up, breaks caught on the PR instead of the queue, dollars per day toward $65 (the $2,000-a-month target), and hours from open to live. Each PR below reports all four.
Partly. Picking tests by what a change imports works; the code is wired so that a change imports almost everything. One in five source files (1,976 of 9,627) selects more than 200 tests on its own (estimate). A front-end/back-end split would not help: back-end tests are 68% of all test files (5,640 of 8,277), and a back-end PR's layer suite is a median 89% of the full suite (estimate).
| Problem | What we measured | Effect |
|---|---|---|
| Import loop | 218 files in one loop (68 leasing, 29 Clara agent, 18 escalation, 14 tool handlers, 12 workflow steps). 919 PRs in 14 days touched it; for 290 it was the only reason they went over the cap (estimate) | Touch any one file, select the same ≥1,419 tests |
| Catch-all data files | Five data-layer files each select 3,900+ tests alone: shared types 4,721 (1,081 direct importers), data helpers 4,323 (546), data front door 3,941 (2,366), JSON repository 3,941, store 3,941 (estimate) | Removing the front door alone leaves 1,966 of the 1,976 big files still big: the tangle sits underneath |
| Always-run set | 941 test files: 715 always-run plus 697 fence pins, 471 in both (measured from config) | ~17× the median PR's own selection (54); ~120× a front-end PR's (8) |
| File-reading tests | 5 of 48 real breaks were tests that read a file or a recording instead of importing it, e.g. #16174, #16750 (measured); none is a guard pin | Invisible to selection; caught only by the full suite (#5549) |
| Skip-cache key | Keyed on tree plus base commit; the base moves ~335 merges a day. 1 of 141 runs skipped (measured) | The cache almost never hits |
Sources: queue full-suite evidence, blast-radius audit, 30-day CI history audit and the pre-boot skip design, all session 002, Oct 5 (on the office Mac mini under the 002 reports folder). Jul 14 cap date from repo history.
Right-sizing CI machines saves about $39/day (about 20% of runner time) and 1.6 min per merge train; no change raises cost.
The 64-core full-set machines are busy (95%) but do not scale: 32→64 cores gave 1.23x, and per core they do about 3.9x less than the 8-core machines running the same suite after merge.
| Job | Now → recommended | $/day | Min per train |
|---|---|---|---|
| 1. Full set (merge queue) | 2 × 64-core → 8 shards × 8-core | −$23 (est.) | −1.6 (est.) |
| 2. Guard pins | 6 × 4-core → 3 × 8-core | −$5 to −$11 (est.) | −0.4 (est.) |
| 3. 2-core x86 helper jobs (code review, gates, watchdogs) | 2-core x86 → 2-core ARM | −$5.5 (est.) | 0 |
| 4. Unit tests (affected), merge queue | 8-core → 4-core | −$2.3 (est.) | 0 median; up to +1 at p90 (est.) |
| 5. cloud-test | 4-core → 2-core ARM | −$0.5 (est.) | 0 |
| 6. Post-merge green/culprit report | 8-core → 2-core ARM | −$0.3 (est.) | 0 |
| 7. Post-merge guard pins | 3 × 2-core x86 → 3 × 4-core ARM | −$0.15 (est.) | post-merge lane −2 (est.) |
| Job | Change | Speed gain | Cost change |
|---|---|---|---|
| PR test with the always-run step | 4-core → 8-core | 13.2 → ~8.4 min (est.) | +10% at measured prices, +31% at flat per-core (about +$0.8 to +$2.5/day) |
| Full set | 2×64 → 3×64, or a 96/192-core box | ~6.2 → ~4.5 min (est.) | +11% or more |
| Post-merge unit-full | 2 × 8-core → 4 × 8-core | 10.4 → ~5.6 min (est.) | +14% |
Type check beside the build (Fede's pick): measured +$20–25/day at on-demand prices; a builder is measuring the real memory peak to find a cost-neutral 16-core box; yes/hold asked.
Session 002 · written 2026-10-05 07:30 MT · numbers from the CI ledger (Oct 4 covers 11:22–24:00; Oct 5 covers 00:00–06:10) and the hourly overnight watch
One line: machine time per merged PR is down a third (1.10 → 0.75 machine-hours), merges got faster, not slower. PR checks are at the goal (−49%); the merge queue is not yet (−14%).
| Per merged PR | Oct 4 (before) | Oct 5 overnight (after) | Change |
|---|---|---|---|
| Machine-hours, all CI | 1.10 h | 0.75 h | −32% |
| of which PR checks | 0.57 h | 0.29 h | −49% |
| of which merge-queue trains | 0.47 h | 0.40 h | −14% |
| Queue trains per merge | 2.1 | 1.7 | fewer rebuilds |
| Trains thrown away (cancelled) | 37% | 28% | 44% at midnight → 5% by 4 AM |
| Merges per hour | 10.9 | 18.5 | faster |
| Queue depth (median) | 28.5 | 10 | shorter line |
| Median good train | 13.7 min | 15.9 min | slower — watching (more hub-sized full runs) |
Hour-by-hour rows, incident log and every decision taken overnight: ~/.claude/logs/ci-overnight-2026-10-05.md and ~/.claude/logs/ci-decisions.log on the office Mac mini.
88 sampled queue failures, 10-01 to 10-03 (every 2nd of 175, across 87 PRs).
| Failed check | Real bug | Flake |
|---|---|---|
| Type check / lint / build | 5 | 40 |
| Guard pins | 5 | 33 |
| Unit tests (touched) | 1 | 3 |
| Full suite | 0 | 1 |
| Before | After | |
|---|---|---|
| Queue run | ~11 min | ~4 min (estimate) |
| One failure in a train | re-runs 11 min for everyone behind | 4 min |
| Merge ceiling | ~44 per hour | ~120 per hour (8 per train) |
| Queue wait, median | 80 min | target under 30 (to be measured) |
| Full suite runs | once per train | once per 15 min, on the newest tip |
Kill switch: revert the queue change with GitHub's Revert button.
Correction (10:20 AM MT): this table was built from a sample with no hub-sized change in it. The “0 of 18 trains ran the full set” row was the bug, not the saving: the full set was being skipped on the trains that needed it, and those trains were failing. Read the incident section before trusting any number here.
| Before (10-01..03) | First 75 min | |
|---|---|---|
| Queue wait, median | 80 min | 17 min (4 merges; quiet Saturday, small sample) |
| Train time | ~11 min | 10 min — the app build is the long pole, not the tests |
| Full set in the queue | every train (2 shards × ~5 min on 64-core machines) | 0 of 18 trains → ~640 core-minutes saved per train |
| Heavy-lane reds | 21 / 11 / 3 per day | 0 |
| Auto-reverts | — | 0 |
| Red main | — | 0 of 4 pushes |
Found while measuring: GitHub fires the heavy lane's every-15-minutes schedule about once an hour (runs at 22:29, 23:28, 00:25, 01:44, 03:06, 06:23, 08:41; repo-wide only 5 scheduled runs fired between 08:00 and 09:40). GitHub throttles schedules on busy repos. Until fixed, a bad merge is caught in ~1 hour, not 15 minutes. Fix in flight: the heavy lane also starts after each completed main CI run, paced to one run per ~15 minutes. No switch.
Sources: GitHub run and job records for ci.yml (merge_group) and post-merge-heavy.yml, PR timelines for queue wait. The estimate row above (“~4 min”) did not hold: the train is bounded by the Next.js build, so the saving is machine time and flake-free trains, not minutes per train.
What happened, in one line: after the thin-queue change merged at 2:21 AM, every train that included a hub-sized change (one touching more than 200 files, which is most of the stacked team-notify work) failed its final gate and was kicked out of the queue. Small trains passed, so the queue looked alive while ~30 approved PRs sat still. Fede noticed at 8:00 AM. A first fix merged at 10:05 AM and did not help; the real fix is #16400.
| Time (MT) | What |
|---|---|
| 2:21 AM | Thin queue merges (#16325). |
| 2:23 AM | First big train fails the gate: “full-set job concluded skipped”. 19 of the next 31 trains fail the same way; 12 small trains pass. |
| 2:22 – 3:37 AM | My first-hour measurement runs. All 4 merges in the window are small PRs. The table above is written from them. |
| 3:16 AM | Web moved to deploy from release; heavy-lane fixes; I watch heavy-lane reds, reverts, queue depth. None of those gauges count a cancelled train. |
| 8:00 AM | Fede: “why are 38 PRs approved and not moving?” |
| 8:12 AM | Root cause found. Fix PR opened (#16396); backup revert opened (#16397), not armed. Admin merge refused by GitHub (queue enforced). |
| 8:45 – 9:56 AM | Fix PR fights CI plumbing: a type error in its own test (my miss), a spot runner dying mid-test (which made the review bot withdraw its approval), and a debounce-cancelled duplicate run that GitHub read as the verdict. Each needed a manual nudge. |
| 10:05 AM | Fix #1 merged. 15 PRs re-armed; 9 trains running within a minute. Small trains pass. |
| 10:15 AM | First big train fails the same way. Diff against the pre-thin-queue workflow finds the real cause (success() + a skipped upstream job). Fix #2 (#16400) opened 10:38 AM, urgent. |
Why the first attempt failed (and the second). Compared with the version that worked before the thin queue, the full-set job’s gating changed in exactly one way: it now also waits on the test job (needs: [plan, test] instead of [plan]). test itself waits on a job that is skipped on every queue train. GitHub’s job-level success() is false when anything in that chain was skipped, so the job’s condition was false on every train, regardless of the mode. The first fix (#16396, 10:05 AM) moved the tier decision inside the job but left success() in the condition, so it changed nothing: the first big train after it (10:15 AM) skipped the job 1 second after test finished. The second fix (#16400) names the two results explicitly (needs.plan.result == 'success' && needs.test.result == 'success'). A drift test forbids success() on that job and has a planted control.
Correction: an earlier version of this section blamed the mode == 'full' conjunct and said GitHub’s evaluation was “unknown”. That was a guess written under time pressure and it was wrong; the diff against the working version answered it in two minutes. Lesson on the page: diff against the last version that worked before theorising.
A second, independent cause was also live overnight and I missed it in this write-up: the merge-queue build machine ran the site build and the type check at the same time and ran out of memory, killing the job (“The runner has received a shutdown signal”, e.g. 3:09 AM). Gera’s #16389 (merged 10:18 AM) runs the type check after the build; the build job has not died since (evidence: 20 runs checked, docs.propflowai.co/a/ci-build-shutdown). Each train now takes about 3.5 minutes longer. So the night had two overlapping failures, one per fix; neither fix alone would have cleared the queue.
Why I did not see it. Three gaps, all mine:
Why it stayed broken ~5.5 hours. Because of the two gaps above, nothing changed between 2:23 AM and 8:00 AM. I was active the whole time (web-from-release flip at 3:16 AM, heavy-lane fixes until ~6 AM) and never ran the one query that would have shown it: trains by conclusion.
Before → after. 2:23–11:36 AM: 0 big trains passed in 9 hours; 6 PRs merged by 10 AM, all small. After 11:36 AM: the first big train passed at 11:40 and 4 big PRs merged in 14 minutes.
One more thing the fix exposed: fix #2 sat in the queue 49 minutes behind six big PRs that kept failing and resetting the line, because the queue rebuilds every train behind a failure. An urgent CI fix has to go to the front: pull the entries ahead of it, let it land, re-arm them. Playbook item.
| Broken window (2:23 – 10:05 AM) | After the fix | |
|---|---|---|
| Trains cancelled by the gate | 19 of 31 | 1 of 7 in the first 20 min, for a real code conflict (#16321 vs #16356) |
| Hub-sized trains that ran the full set | 0 | every one (4 of 4 by 11:54 AM; both shards, ~5–7 min) |
| PRs merged | 6 in 7.5 h | 4 big + 8 small in 20 min (fix #2 merged 11:36 AM) |
What changes so this cannot repeat.
A PR that lets Clara answer a renter’s texted question (#16333) went through the queue green at 3:50 PM. Eighteen minutes later the post-merge checks failed: the PR added a new Clara tool without registering it in the tool inventory, and the test that checks that registry never ran on the train.
| Time | What happened |
|---|---|
| 3:50 PM | #16333 merges; its queue train ran the full test set and the full guard-pin set, all green. |
| 4:12 PM | Post-merge heavy lane fails: tool-inventory drift test, 5 assertions. |
| 4:20 PM | Driver claimed (session 002). Revert #16406 opened, marked urgent, put at the front of the queue. |
| 5:07 PM | Revert merged and promoted to production at once. 5:32 PM: post-merge checks green again. |
| 5:50 PM | Root cause found; fix PR #16410 opened (see below). |
The test fell between two lanes. The queue’s full test run skips every guard-pin test on the assumption the guard-pin phase runs them. The queue’s guard-pin phase skips the ‘bookkeeping’ pins on the assumption the post-merge run covers them. This test was classed as bookkeeping, so on a queue train it ran in neither place; only the post-merge run saw it.
One rule, enforced by a test: the queue may skip a test in the full run only if the guard-pin phase on that same train runs it. Bookkeeping pins now stay in the full run. The tool-inventory test is promoted to a blocking pin because it reads the whole tool tree, so no code change ever ‘selects’ it. The test ships with a mutation control: putting the old exclude list back turns it red.
I first reported a second failure (a leasing guard). It was output from a passing test. I also first blamed the affected-test selection; the train had run the full set. Lesson, same as the morning: read the train’s logs before naming a cause.
Pinterest's pipeline runs a dependency graph, then targeted tests, then a thin queue, then a post-merge check. We have the same shape. Our “services” are directories in one repo.
| What | Where | State |
|---|---|---|
| Safety net (post-merge full suite, revert bot, blessed release) | #16314 | merged |
| Queue change (thin queue) | #16325 | merged 2:21 AM MT |
| Fix #1: full-set tier decided at run time (did not fix the skip) | #16396 | merged 10:05 AM MT |
Fix #2: no success(); names the needed results | #16400 | merged 11:36 AM MT; first big train green 11:40 |
| Backup revert of the thin queue | #16397 | closed 11:56 AM MT, not needed |
| The reversed decision (full suite back in the queue) | #15178 | reversed |
| History | ADR-0107 | earlier decision |
About 55% of our machine time goes to rebuilt trains and re-run PR checks, so these levers go after that waste.
| # | Lever (plain words) | Expected effect | Effort | Risk | Status | Source |
|---|---|---|---|---|---|---|
| 1 | Split the waste by cause: Count minutes lost to rebuilt trains, re-run PR checks, flakes and idle CPU on the big machines | None directly; shows which later lever is worth the most | Low | None | NEW | RunsOn costs |
| 2 | Retry only failed test files; quarantine repeat flakes: A flaky test stops kicking a good PR out of the train | Fewer rebuilt trains (root of the rebuild cascade) | Low-Med | Low (needs a “failed twice = real” rule) | NEW | Trunk |
| 3 | Skip a check whose code is identical: If the train’s code tree matches one already proven green, reuse that result | Cuts re-run PR checks and the first PR in a train | Med | Low-Med (tree must match exactly) | NEW | GitHub queue docs |
| 4 | Cancel superseded PR-check runs: A new push kills the older run of the same PR (PR runs only, never queue runs) | Fewer wasted PR-check minutes from review-fix pushes | Low | Low | NEW (verify we lack it) | GitHub concurrency docs |
| 5 | Cheap checks cancel the big test job: Run lint and typecheck alongside tests; if they fail, stop the 64-core job at once | Doomed runs stop early; no delay for good PRs | Low | Low | NEW | GitHub concurrency docs |
| 6 | Cache test results by input hash: Skip a test group when its files and dependencies are unchanged since a green run | Largest minute cut after 2 and 3 | Med-High | Med | NEW | RunsOn S3 cache |
| 7 | Balance shards by time; size machines to real CPU use: If the 64-core machines sit idle at the tail, split by test duration or use a smaller box | Fewer machine-hours at the same wall time | Low-Med | Low | NEW | RunsOn costs |
| 8 | Small jobs on small machines: Lint, typecheck and bot steps should not run on big boxes | Fewer machine-hours | Low | Very low | NEW (verify) | RunsOn costs |
| 9 | Larger queue groups (2-3 PRs per train): Fewer trains for the same merges | Fewer builds, but a failed group needs a bisect | Low (setting) | Med | NEW (verify first) | GitHub queue docs |
| 10 | Graviton (ARM) machines: Same work on cheaper hardware | Lower price per hour, not fewer minutes | Med | Med (native modules) | NEW | AWS Graviton |
| 11 | Independent lanes in the queue: An unrelated PR’s failure does not rebuild everyone behind it | Attacks the cascade at its root | High | Med-High (replaces the queue) | NEW, last | Aviator |
| 12 | Savings plan: Discount on on-demand machines only | Small unless on-demand share is large | Low | Low | NEW, low value | AWS |
| - | Spot “capacity-optimized”, circuit breaker: RunsOn already defaults to it | None | - | - | ALREADY HAVE | RunsOn |
| - | Predictive test selection (Launchable, Datadog): We already select affected tests | None | - | - | ALREADY HAVE | Machalica 2019 |
| - | Nx / Turborepo migration: Too big a rewrite; take only the hash-cache idea (row 6) | None | - | - | REJECTED | — |
| - | Warm pools, CodeBuild Lambda, Dependabot grouping, artifact retention: Trains run 13 min, so a 25-second start is noise; the rest save pennies | None | - | - | REJECTED | — |
| - | Lower queue depth: Trades velocity for cost | None | - | - | REJECTED (Fede) | — |
Try first, in this order: measure the waste by cause; retry only failed test files and quarantine flaky ones; reuse a green result when the code is identical; cancel older runs of the same PR; let cheap checks stop the big test job early; then save test results from the 15-minute main lane for the queue to reuse. None of this lowers queue depth or slows a merge.
No dollar figures: the baselines in the raw notes were not ours.
Status: Proposed — pending Fede’s pick. Today’s 8-hour outage was watched by nobody: the session that owned CI was busy on the next task and had no alert. The job below is small, boring and permanent: look at five numbers every 15 minutes, act by playbook, page a human only for the two things a human must decide.
| A. A job inside Agent Smith (Gera’s system, Mac mini) | B. A dedicated CI seat on the Mac mini (own launchd daemon, claude -p turns, like Trinity) | C. GitHub-native only (workflows that page and auto-rerun; no agent) | |
|---|---|---|---|
| Already has | merge-queue watcher, deploy watchdog, red-main claimer, steward sweep, Slack seat | nothing yet; the hooks exist (gh-event-forward, pr-event-wait, red-main-claim) | deploy-queue-watchdog, auto-merge-all, revert bot |
| Can it fix forward? | yes (writes PRs today) | yes | no: it can rerun, page and revert, not write a fix |
| Who merges changes to it | Gera only (“nothing merges into agent-smith without Gera”) | us, same day | us, via ci.yml PRs |
| Time to first useful version | days (through the bridge, Gera’s queue) | 1 day: playbook + 5 gauges + Slack alert | half a day |
| Cost | Gera’s time; Smith’s tokens | ~1 Opus turn / 15 min when something is off, Haiku otherwise | near zero |
| Risk | two owners of one system | another daemon on the mini (admission control exists) | pages a human for everything it cannot fix |
Recommendation: C now, B this week, A later if Gera wants it. C is the part that would have caught today in minute two (two cancelled trains in a row → Slack alert) and costs nothing; it ships as ordinary ci workflow PRs. B is the part that fixes forward at 3 AM without an open session; it runs the playbook below and nothing else. A folds B into Smith once the playbook has run for a few weeks and Gera has seen it work; a draft of B through the bridge first costs days we do not have.
| Signal (checked every 15 min) | Wait | Intervene (no human) | Alert (Slack: #agent-trinity; Fede DM only if he asked) |
|---|---|---|---|
| Merge-queue train cancelled | 1 cancel: normal (a real conflict is what the queue is for) | 2 in a row with the same failing job: read the gate error; if it names a job that skipped → look for success() + a skipped upstream job; if a runner death → gh run rerun | 3 in a row, or any cancel whose failing job is in .github/: page, with the gate line and the last ci.yml merge |
| Green PR, armed, not in the queue for >15 min | — | the three plumbing traps: approval withdrawn after a spot kill → gh workflow run claude-code-review.yml -f pr_number=N; debounce-cancelled duplicate run is the newest → gh run rerun; required context missing → re-fire it | still stuck after both: page |
| Urgent CI fix in the queue | — | move it to the front: dequeue the entries ahead, let it land, re-arm them (today: 49 min lost behind the PRs it fixed) | — |
| Red main (push CI failure) | — | claim the driver slot; if the red is the last merge and the fix is not obvious in 20 min → revert that one commit (revert doctrine) | page on the revert, one line |
| Post-merge heavy lane red on a commit whose train was green | — | Claim driver, revert the introducing merge with [urgent] to the front of the queue, then find which lane skipped the failing test; a test skipped by one lane must be run by the other on the same train. | — |
| Heavy lane red (post-merge full suite) | 1 red from a known flaky file: wait for the rerun | “environmental” classification → rerun; a culprit commit → revert bot already does it | 2 reds in a row with different culprits: page |
Release lag (main ahead of release) | < 45 min: normal (heavy lane cadence) | > 45 min and heavy lane green: check the advance step, rerun | > 90 min: page |
| Queue depth | < 8 entries | ≥ 8 for 30 min: look for the head entry’s train; if it is failing, treat as row 1 | ≥ 15: page |
| After any ci.yml merge | — | measure the next hour with at least one train of each shape the change treats differently; a 0 in any “ran” column is a red flag, not a saving | no hub-sized train in 2 hours: fire a workflow_dispatch dry run on one |
| Spot runner killed mid-job | — | attempt 2 is automatic; re-fire the review if the approval was withdrawn | ≥ 5 kills/hour: page (switch the pool to on-demand for the day is Fede’s call) |
Rules the agent keeps: diff against the last working version before theorising (today’s first RCA was wrong because I did not); every intervention is one line in ci-decisions.log and one line in the Slack thread; it never merges, closes or splits another lane’s PR, only re-arms a green one; it never touches a customer; and a human is paged with one plain line and a recommendation, never a wall.
What it is not: a reviewer, a builder of features, or a second owner of Smith’s lanes. It watches, nudges, fixes CI plumbing, and wakes the right owner.
We test the same change about seven times. Compute is mostly avoidable (82–88%, estimate; the measured sample above puts guards at about a third of first failures, not most); the tests that catch real bugs mostly are not.
property-owner-id-retired) stopped a PR from reading a credential by a retired field.| Day | Gross | CI machines (EC2) | Product (DynamoDB, Lambda, logs, API, rest) | Note |
|---|---|---|---|---|
| Sep 24–28 (avg) | $58 | $0 | $58 | CI still on GitHub-hosted runners |
| Sep 29 | $347 | — | — | CI moves to AWS runners |
| Sep 30 | $450 | — | — | credits run out at month end |
| Oct 1–3 (avg) | $531 (estimate: $1,592 usage ÷ 3; Oct 1 and 2 individually not pulled) | — | — | first days the page shows real dollars |
| Oct 3 | $429 | $324 (76%) | ~$105 (estimate: ~$45 of it is CI-induced, by subtraction from the $58 pre-CI baseline; public IPs $13 are measured) | no cuts in it: the thin queue went live Oct 4, 2:21 AM MT |
September usage was $4,012, covered in full by credits (so the spending page read ~$0). The CI bill did not grow on Sep 29; it moved from GitHub’s invoice to AWS and lost the credits. Of Oct 3’s CI machines, the two 64-core spot machines that ran the full suite on every train (c7g/c8g.16xlarge) were $102; the thin queue removes them from every train that is not hub-sized, a realistic $60–100/day. CI then sits near $250/day. A “dramatic” cut (CI at ~$65/day, $2k/month) only comes from running each change fewer times (today ~7 test runs per change: every push, every bot verdict, the queue, post-merge), which is the point of the five moves above. Dollar per move: next section to fill, with the first clean day (Oct 5) as the baseline.
Source: aws ce get-cost-and-usage, account 194925971150, Oct 4 12:05 PM MT. Oct 3 may still grow a few percent as Cost Explorer settles.
| Item | Verdict |
|---|---|
| Cancelled queue runs (50% of compute) | Delete check against fresh main first |
| Full set once per queued PR (25%) | Change once per batch, after merge |
| Guard shards in the queue (~5%) | Delete from the queue |
| Guard shards on PRs (~5%) | Shrink to ~20 incident-backed guards, rest nightly |
| 383 tests about CI's own shape (~2%) | Delete as gates |
| 46 verdict and placeholder jobs (<1%) | Delete |
| Re-runs on every review verdict (1,423/day) | Delete the trigger |
| Product unit tests (~6,800 files) | Keep affected subset on PR, full once per batch |
| Eval and replay tests | Keep and grow |
| Authorization-scope tests | Keep as a merge gate |
The PR lane is about 9% of daily compute, so local habits are a few percent of cost. But they decide how many times a PR enters the line and whether it fails there.
Pulled by the two seats from their last 60–79 merged PRs (2026-10-03). Counts are real; interpretation is labeled where it is an estimate.
| Agent Smith's seat (Gera's lane) | Trinity's seat | |
|---|---|---|
| Pushes per PR before green | Median 2, average 2.3; worst 10% at 4+. 29 of 60 landed on one commit. Only 5 of 60 pushed in a merge of main. | Average 2.66 extra pushes per merged PR, median 1. 34.5% of PRs get 3 or more, and those make 83% of extra pushes (79 PRs). |
| What drives the extra pushes | Smith's read, not measured: mostly a review-bot 🟡/🔴 finding, then a red check, rebases last. | Measured: pushes after a review verdict (15 of 23 after a 🟡, 8 of 25 after a 🟢, despite the rule), then rebases onto main (about 24% of extra commits). |
| What runs locally before a push | npm run review:self, typecheck, touched test files only — never a full vitest run. | typecheck and review:self about once per push. Guard tests: never. |
| Hand re-triggers of review/CI | 1 hand @claude comment across all 60 PRs; re-reviews mostly fire on their own on a new commit. Hand re-runs of CI jobs not measured. | Not measured; re-reviews fire on their own on each new commit. |
| Habit that wastes runs | Re-queueing a PR the merge line kicked out with no change (#15346, tonight); queueing several PRs from one session at once (three at 3:47 PM CT). Both happen because a PR's own CI run doesn't include the guard tests the queue runs, so it looks green when it isn't. | Updating the branch from main mid-PR (the pre-push hook text tells sessions to rebase), and pushing after a 🟡 verdict. |
| Habit worth copying | Run the queue's guard tests locally against the touched files before marking a PR ready — would have caught all four of tonight's kick-outs on the laptop instead of in the line. | A local push hook (live Oct 3) caps a PR at two CI rounds and blocks pushes after a verdict; run the line's guard set before the first push. |
| Habit today | Change | Saves (estimate) |
|---|---|---|
| Branches get updated from main mid-PR (about 24% of extra commits; the pre-push hook tells sessions to rebase) | Stop. The line re-tests on fresh main anyway. | About 80 runs a day |
| 2.66 extra pushes per merged PR on average. 34.5% of PRs get 3 or more, and those make 83% of extra pushes. | Hook-enforced two-round cap | Up to half of extra pushes |
| Pushes after a yellow verdict (15 of 23) or a green one (8 of 25), despite the rule | Hook blocks the push; the finding becomes a follow-up issue | Fewer review rounds |
| Guard tests are not run locally | Run the always-run guard set (about 60 s) before the first push | Fewer line failures (unsized) |
Already working, keep: typecheck and review:self run about once per push. Docs-index, lockfile and PR-body fix-up commits are now zero in the sample. --no-verify is about 1 per two weeks.
Caveats: 79-PR sample (plus or minus 10 points). "Pushes" is an upper bound on runs. Estimates overlap, so do not add them.
After the overnight build. Ordered by impact. Owner in brackets.
What "agents run CI" means: the operator is a swarm, so every rule has to be mechanical and every decision written where the other swarm reads it. Oct 2–3 worked because both swarms agreed one merge order in writing; it was slow because one file forced it. Item 9 removes the need, not item 8.
| Move | A | B | C | Recommendation |
|---|---|---|---|---|
| 0 Deploy from last green batch | Keep deploying main tip | Vercel deploys a release branch advanced by the batch run | Same as B plus canary % | B now, C with move 5 |
| 1 Test once, in groups | Tune GitHub's queue | Buy a batching queue | Thin queue, full set once per batch after merge | C now, B as fallback |
| 2 Demote the guards | Delete the 1,155 guard files | Run them nightly | Fail only on new violations | B + C; delete the 383 and 46 |
| 3 Never rerun unchanged | Skip a passed code tree | Cache per test file | Move to Nx or Turborepo | A now, B next month |
| 4 Cap rounds | Two CI rounds per PR | Agents check locally first | Both, and stop re-firing on verdicts | C |
| 5 Safety where incidents come from | Canary and instant rollback | Revert bot with daily cap | A and B, plus heavier checks on risky paths | C |
Caveats
Research notes: 13 files under ~/agents/002/research_notes/Agentic era CI at scale/ (web sources, talk transcripts, podcasts and books, local practices).
396 written sources, 24 talk transcripts, 8 podcast episodes (show notes), 4 books via free chapters and summaries, plus our own repo, bill and session data.
Research notes: internal audit, six months of CI cost and speed, PR size economics, merge-queue designs, post-merge testing and progressive delivery, test selection and flaky tests, which CI gates prevent defects, CI cost engineering, CI vendors and landing platforms, agentic-era CI practices, pure-agentic delivery and 10× volume.
PropFlow's CI bill comes mostly from running the same checks on the same code again and again and throwing most of the results away. Machine prices are not the problem, and neither are the tests that catch real bugs. On October 2 the pipeline used about 1.27 million core-minutes (≈$360). Half of that went into merge-queue runs that were cancelled. Another quarter went into re-running the full ~8,000-file test set once for every PR waiting to merge (research note: internal audit). In a 48-hour sample of merge-queue failures, 77% (86 of 111) came from the 14% of test files that police the repo's shape (pinned counts, drift checks, tests about CI itself). Those failures were not product bugs. Nine of ten September production incidents were the kind none of the 8,000 tests could catch (same source).
Leading teams converge on one design: a thin, fast check before merge, one full run per batch after or at merge, automatic culprit-finding and revert, and a canary with rollback. No team that publishes its pipeline has dropped automated CI, even with AI writing most of the code. What they dropped is repetition, unbounded retry loops, and human waiting. Applied here, that design plausibly cuts compute 82–88% and lifts the merge line's throughput ceiling 5–10×, both estimates. The move to small PRs was worth it for review, and its CI penalty disappears once CI stops testing every PR separately. GitHub's machines are not the bottleneck. Its merge queue design and its API limits are. Renting AWS machines directly was the right call on price. The move went wrong because the plan was priced at August volume and startup credits hid the real bills on both sides.
Contents
| # | Move | Option A | Option B | Option C | Recommendation |
|---|---|---|---|---|---|
| 1 | Test each change once, in groups | Keep GitHub's merge queue and tune depth and batch size | Buy a queue that runs one test for a batch of PRs, finds the bad PR by splitting the batch, and retries a flaky failure before evicting (Trunk, Mergify, Aviator, Graphite) | Keep a thin merge queue (type check plus affected tests only). Run the full set once per batch after merge, then bisect (split a failing batch in half and retest until the culprit is found), revert it automatically, and deploy through a canary | C now, B as fallback. A cannot beat the (1−p)/p ceiling below. GitHub's native queue still starts one run per queued PR even when a group merges together, so "once per group" needs B or C (not verified; first open question below) |
| 2 | Demote the guard layer | Delete the 1,155 guard files | Run them nightly. They open one issue per failing family and never block a merge | Turn exact-count pins into "fail only on new violations" ratchets | B + C for guards; delete outright the 383 tests about CI's own shape and the 46 jobs that only report other jobs' results. Keep about 20 guards with a real incident behind them as merge gates |
| 3 | Never run a test whose inputs did not change | Skip a run when the exact same code tree already passed | Cache each test file's result, keyed on a hash of the file plus everything it imports, stored in S3 | Migrate to Nx or Turborepo packages | A this week, B next month. C pays off little in a monolith (Mercari) |
| 4 | Cap rounds and verify before pushing | Hard cap of two CI rounds per PR, then escalate | Agents run type check and affected tests locally before every push | Both, and stop re-firing CI on every review verdict | C. Stripe caps agents at two CI rounds (Stripe). We average about 2.2 pushes per merge and had 1,423 review-triggered re-runs on Oct 2 |
| 5 | Spend the safety budget where incidents come from | Canary plus instant rollback on Vercel and Lambda | Bot that bisects post-merge failures and opens reverts, with a daily cap | A and B, plus heavier pre-merge checks only for risky paths (auth, permissions, money, outbound messages) | C. Our incidents came from configuration, providers and end-to-end behaviour, and canaries catch that class |
Why the merge line cannot keep up. In a merge queue where each PR is tested on top of the ones ahead of it, one failure throws away every run behind it. Say each queued PR fails with probability p. Then merges per round can never exceed (1−p)/p, however deep the queue (research note: merge queue designs). Uber measured exactly this ceiling (EuroSys'19). Our per-entry p has not been measured directly. Queue runs failed 28% of the time in the Sept 30–Oct 2 window (research note: six month history), which suggests p of roughly 20–30% (estimate). The merge line passed 24–42% of its runs (the figure in the brief). On Oct 2 it was 702 of 2,214, or 32% (research note: internal audit).
| Per-entry failure rate p | Merges per round, queue depth 10 | Ceiling at any depth |
|---|---|---|
| 30% (our likely range, estimate) | 2.3 | 2.3 |
| 20% | 3.6 | 4 |
| 10% | 5.9 | 9 |
| 5% | 7.6 | 19 |
| 2% | 9.0 | 49 |
With ~10-minute rounds, a ceiling of 3 means about 18 merges per hour against peak arrivals of 30–50 per hour (estimate). That gap is why the queue grew to 26 entries at 46 arrivals per hour on Oct 2 (research note: six month history). The lever is p, not depth.
Where Oct 2's compute went (≈1.27M core-minutes ≈ $360, ±25%) (research note: internal audit):
| Bucket | Share |
|---|---|
| Merge-queue runs cancelled and thrown away (1,511 runs) | 50% |
| Merge-queue runs that passed (702), of which the 64-core full test set alone was 25% of the day | 34% |
| PR checks (1,475 runs) | 9% |
| A measurement-only shadow job (now off) | 4% |
| Deploys on push to main | 1.3% |
| Review bot, fences, Lambda deploys, review-triggered re-runs | 1.3% |
Per merged PR, the basic checks ran about 7 times, the six guard shards about 7 times each (42 shard jobs), and the full set about 5 times, about 3.4 of them cancelled. About 60% of all compute repeated a run that had already happened or was never allowed to finish (same source).
14% of tests produce 77% of queue failures. Of 7,995 test files, 1,155 (14%) are guards. They read source files as text, scan the repo, pin exact counts, or police CI. In a 48-hour sample they caused 86 of 111 named merge-queue failures, and 50% of the failing runs had no other cause. Each guard passes alone and fails when a sibling PR moves main underneath it. In 150 sampled failures, zero were the type or build clashes a merge queue exists to catch. In the PR lane, about 41 of ~70 failure signals were the pipeline failing on itself (same source).
Six months of cost. Credits hid both bills (research note: six month history):
| Month | Merged PRs/day | GitHub Actions (org-wide) | AWS CI (before credits) | Median open→merge |
|---|---|---|---|---|
| Apr | 17 | $136 | $0 | ~0.2 h |
| May | 38 | $1,002 | $0 | ~0.45 h |
| Jun | 31 | $621 | $0 | ~0.4 h |
| Jul | 72 | $1,166 gross / $864 net (Jul 14–31 only) | $0 | ~0.5 h |
| Aug | 45 | $2,659 gross / $800 net ($10k startup credit from Aug 17) | $0 | ~0.5 h |
| Sep | 190 | $10,904 gross / $2,239 actually charged (credit ran out Sep 25) | ~$1,280 CI + ~$1,860 one-off AWS Config/Security Hub (covered by AWS credit) | 0.4 → 0.9 h |
| Oct 1–2 | ~510 | ~$21–25/day | ~$280–440/day (estimate; AWS credit runs out ~Oct 3) | ~2.6 h |
| Period | $/day | Merges/day | $ per merged PR |
|---|---|---|---|
| GitHub, Aug 1–17 | $65 | 45 | ~$1.45 |
| GitHub, Sep 22–28 | $708 | 282 | ~$2.50 |
| AWS, Oct 1 (estimate) | ~$383–438 | 585 | ~$0.70 |
| Target ($65/day at ~500 merges) | $65 | 500 | ~$0.13 |
PR size (all 7,557 merged PRs, Aug 3–Oct 1, no sampling; CI runs counted on the PR side only) (research note: pr size economics):
| Lines changed | Merged | Median time to merge | First AI review clean | Pushes after review | Review threads | PR runs per merged PR | PR runs per 1,000 lines | Reverts per 1,000 PRs |
|---|---|---|---|---|---|---|---|---|
| ≤100 | 1,509 | 0.24 h | 66.5% | 0.54 | 0.39 | 1.79 | 41.1 | 2.7 |
| 101–300 | 2,120 | 0.51 h | 40.7% | 1.31 | 1.07 | 2.43 | 12.4 | 3.3 |
| 301–600 | 1,826 | 0.80 h | 22.5% | 2.29 | 1.97 | 3.50 | 8.1 | 5.5 |
| 601–1,000 | 1,065 | 1.25 h | 10.0% | 3.89 | 3.68 | 5.23 | 6.7 | 5.6 |
| >1,000 | 1,037 | 2.04 h | 5.8% | 6.36 | 7.06 | 7.99 | 3.0 | 5.8 |
| Period | PRs merged/day | PR runs per 1,000 merged lines | First AI review clean | Median time to merge | Share of lines from 1,000+ line PRs |
|---|---|---|---|---|---|
| Before the small-PR rule (to Sep 9) | 50 | 3.7 | 24% | 0.64 h | 80% |
| Small-PR rule (Sep 10–28) | 227 | 7.3 | 32% | 0.63 h | 41% |
| Size gate (Sep 29–Oct 1, 3 days) | 444 | 9.0 | 43% | 1.28 h | 19% |
The largest published systems do not run the whole suite on every change before merge. At Google, presubmit runs only fast, reliable tests, with an average wait of about 11 minutes. A change that passes has a 95%+ chance of passing everything else. The slow tests run after submit, in batches. A failing batch is automatically split to find the culprit, and rolling back is the default fix (SWE at Google ch. 23). Google gave up testing every commit separately because compute grew with submissions times tests (Memon et al. 2017). Meta runs fewer than a third of the dependent tests and still catches over 99.9% of faulty changes. Everything runs on main every few hours, and that run catches the rest (Machalica et al.). Shopify ran about 60% of tests per build with 99.94% recall. Only five failing tests escaped in 8,360 merged commits, because the full suite runs on every deploy (Shopify).
Merge queues at volume batch PRs and retry before evicting. Shopify tests batches of 8 PRs, runs 3 batches at once, and evicts a PR only after repeated failures (Shopify). Its P90 merge time fell from 693 to about 41 minutes after it split the queue into independent lanes (Graphite case study). Uber's queue took its iOS main branch from green 52% of the time to always green (EuroSys'19). It later found 40–65% of its speculative builds were aborted. Running only the builds likely to matter cut CI resources 53% (arXiv 2501.03440). The research agrees. Batching with bisection used 72% fewer test runs than testing each commit, and only 42% fewer once flaky tests entered (Najafi et al.). On 285M test results, batching saved 88% of machines and missed zero failures. Test selection alone missed up to 55% (Fallahzadeh et al.).
Agent-first teams add three habits. Stripe merges 1,000+ agent-written PRs a week. Lint runs locally in under 5 seconds, CI selects from 3M+ tests, and the cap is two CI rounds before a human steps in (Stripe). OpenAI's harness team kept builds under a minute and kept blocking gates minimal ("corrections are cheap while waiting becomes expensive"). Flaky tests get follow-up runs instead of blocking (mirror of OpenAI post). Meta's RADAR lands 25K+ low-risk diffs a day with no human review. They get reverted at one-third the rate and cause incidents at one-fiftieth the rate of other diffs, because a model trained on past reverts decides which diffs qualify (arXiv 2605.30208). Across 153k merges, 94% still go through queues one at a time. AI-assisted PRs broke main 1.9% of the time against 4.4% for others, and teams of 2–5 broke it 0.77% of the time (Mergify 2026). Team size predicts breakage far better than AI authorship does.
We have no "test once" principle. Every published system tests a given code tree once, or once per batch. We test it on the PR, again on each queue rebuild (5.1 queue runs per merged PR on Oct 2), again after review verdicts, and until recently again on main (research note: internal audit). Small open-source repos that skip runs whose exact code tree already passed went from 37 to about 4 machine-minutes per merge (hydrozoa #767). GitHub's queue reuses nothing from the PR run (GitHub Docs), so the reuse has to be ours.
We have no admission bar for gates. Google lets a check block a commit only if it reports correctness issues and has essentially zero "effective false positives", meaning findings developers ignore or work around. To appear at review at all, a check needs under 10% (SWE at Google ch. 20). A guard whose usual fix is "bump the number" fails that bar by definition. Google calls these change-detector tests and calls them harmful (Testing Blog). Agents make it worse. A 2026 study found agent-written tests lean toward print statements and exact-value checks, and they barely change task outcomes (arXiv 2602.07900). Our test code is now 113 MB against 82 MB of product code (research note: internal audit).
CI feeds on itself. The main workflow file is 7,201 lines. Workflow files took 1,096 edits in 14 days. 23% of commits on main touched no product code, and 22% touched CI or test infrastructure. The loop runs like this. A guard breaks a sibling. Someone adds a control to prove the guard can fail. A manifest pins the guards. A test checks the manifest. Then the manifest disagrees with itself, which caused 9 of 40 sampled PR failures (same source). Each layer is reasonable on its own, and together they are the largest single source of red.
The queue has no answer for flakes. GitHub's queue evicts on the first failing status and rebuilds everything behind it. It has no bisection, no retry-before-evict, and no independent lanes (research note: merge queue designs). Every flake therefore costs a full round for every PR behind it. Google measured flaky results on about 1.5% of test runs and spent 2–16% of compute re-running them (Micco). Slack cut its test-job failure rate from 57% to under 5% by automatically disabling flaky tests and assigning each one to an owner (Slack).
The safety net sits in the wrong place. September's incidents were these: a setting switched on by mistake, email gating logic, a cross-company data leak, an AppFolio site change, a voice provider outage, the CI spend itself, voice dead-air, a voicemail detector made default without end-to-end evaluation, and home-matching logic (research note: internal audit). Only the data leak is a class that a pre-merge test (an authorization-scope test) is built to catch. The rest need canaries, replays on real traffic, and fast rollback.
Nobody owns cost per merged PR. Every AWS projection used August's 45 merges a day. Credits masked both bills, and nobody tracked the metric that matters: dollars per merged PR (research note: six month history).
Fede bet that 80% of the compute and jobs are unnecessary. The data supports that for compute, and for jobs it is conservative. Of 56 jobs in the main workflow, 10 do work. The other 46 turn other jobs' results into check names, or post "(not applicable)" so required-check lists stay satisfied. A typical PR shows ~170 check names, 141 of them skipped (research note: internal audit). The audit's redesign lands at 12–18% of today's compute, about $45–65 a day (estimate). An earlier internal cost model put the recommended package at ~$176 a day, with a $116 stretch. It said $65 needs test selection by changed files, ~1.1 pushes per PR, and ~1% bad PRs (research note: six month history). The two estimates differ on one question: does the design remove queue repetition structurally, or tune it? Only structural removal reaches $65.
| Item | Share of compute | Real defects caught | Verdict | What covers the risk instead |
|---|---|---|---|---|
| Cancelled queue runs | 50% | 0 by definition | Delete. Check against fresh main before queueing; eject a failing PR in one round | Nothing needed |
| Full set once per queued PR | 25% | The same 3 real bugs a once-per-batch run would catch | Once per batch, after merge (Move 1C) | Post-merge run, bisect, revert, canary |
| Guard shards (slices of the guard tests, each on its own machine) in the queue | ~5% | Sibling collisions only | Delete from the queue | Run once on the PR against fresh main |
| Guard shards on PRs | ~5% | ~3 of 40 sampled (route inventory, protected classes, smoke password) | Shrink to ~20 incident-backed guards, one 2-minute shard; the other ~1,100 run nightly | Nightly issue, review bot |
| 383 tests about CI's own shape | ~2% | 0 product defects, 41% of queue reds | Delete as gates; keep ≤10 weekly | One owner for the workflow file |
| 46 verdict and placeholder jobs | <1% | 0, but 23 of 40 PR reds | Delete | Three required checks: checks, tests, queue verdict |
| Re-runs on every review verdict (1,423/day) | ~0.5% | 0 | Delete the trigger | Bot already posts its verdict |
| Product unit tests (~6,800 files) | inside the full set | The real bugs | Keep: affected subset on PR, full once per batch | — |
| Eval and replay tests | small | 5 of ~14 real PR catches | Keep and grow: highest yield per minute | — |
| Authorization-scope tests | small | The class behind the Sep 8 data leak | Keep as a merge gate | — |
The deletions carry a risk. Architecture drift may take a day to show up instead of minutes. The evidence says that trade is safe: none of September's incidents was drift of that kind, and Meta found more post-merge faults from PRs landing in parallel than from tests it skipped (Machalica et al.).
Yes, the move was worth it. Above 1,000 lines, the first AI review comes back clean only 5.8% of the time, against 66.5% at 100 lines or fewer. Pushes after review run 12× higher (6.36 vs 0.54), review threads 18× higher, and median merge time is 8.5× longer (2.04 vs 0.24 hours) (research note: pr size economics). Reverts rise steadily with size, from 2.7 to about 5.5–5.8 per 1,000 PRs above 300 lines, though 33 reverts is too few to be firm. Graphite's industry data has the same shape: PRs over 500 lines get updated about 5 times after opening (Graphite). Over the three periods, the share of lines from 1,000+ line PRs fell from 80% to 41% to 19%, and the first review came back clean 24%, then 32%, then 43% of the time. Under the 1,000-line limit (Sep 29–Oct 1, 3 days), merges per day reached 444, with 43% clean first reviews and 9.0 CI runs per 1,000 lines. PRs over 1,000 lines are still 13.7% of merged PRs but 57% of merged lines (same source).
The CI side has a real cost. Merged PRs per day went 50 → 227 → 444, while merged lines per day only went 58k → 113k → 162k, so PR-side runs per 1,000 merged lines rose 3.7 → 7.3 → 9.0. Tiny PRs are the worst per line: 41.1 runs per 1,000 lines, and 7.9% of them close unmerged (same source). Median merge time doubled to 1.28 hours under the size gate, but that period is only three days and coincides with the merge queue coming back (26% of merges went through it, and 13.8% of those were kicked out at least once), so the slowdown cannot be pinned on PR size. The CI penalty exists because every PR gets its own queue run and its own main-branch run. With batching, CI cost per merged PR falls almost N-fold for a batch of N (Mergify). Group-testing math puts the best batch near 1/√p: about 7 PRs at a 2% failure rate, about 3 at 10% (estimate) (research note: merge queue designs). Verdict: keep the 600- and 1,000-line gates. Aim for 100–600 lines. Fold non-urgent sub-100-line follow-ups into the next PR in the same lane. Fix the CI cost through batching, not bigger PRs. One caveat: the retro's claim that 1,000+ line PRs were "7% of PRs but 38% of blocking findings" does not match this data (13.7% over 60 days, 3.8% under the gate), and blocking findings by size were not pulled. Treat it as unverified.
The per-minute promise held. AWS ran about $0.0014 per machine-minute on Sep 30, against GitHub's $0.005–0.014 (research note: six month history). Four things broke the plan. First, it froze August volume (45 merges a day) into every projection ($300, $680 and $1,100 a month), while merges grew 6–13× and machine-minutes 6–18×. Second, credits hid both bills. The "$10k GitHub month" was gross, and the card was charged $2,239. GitHub's credit ended Sep 25 and AWS's runs out around Oct 3 (estimate), so both became real cash within about a week of each other. Third, there were side costs and switch mistakes. AWS Config and Security Hub recorded every short-lived runner, costing ~$1,860 in 5 days. The AWS runners were switched off during an AWS outage on Sep 21 and left off ~7 days at peak volume, costing $1,909 in real GitHub cash over Sep 25–28. 1.26M of September's minutes ran on GitHub-hosted jobs that never moved. A full quota for spot machines (AWS spare capacity sold at a discount that AWS can take back) pushed ~72% of AWS machine dollars onto on-demand machines on Sep 30, with 66k–134k launch failures a day. Fourth, the merge queue came back on Sep 30 without batching. It threw away half its runs, and median open-to-merge rose from ~0.5 to ~2.6 hours. One session claimed AWS cost "$5–6/day" and the true figure was ~$310 (same source). The most accurate line in the record is from Sep 29: "the savings are in minutes, not the vendor."
The table prices a 64-core, 15-minute run, close to our full set (64 cores × 10.9 minutes) (research note: vendors and platforms):
| Platform | $/run | $/month at 500 runs/day | $/month at 2,000 runs/day |
|---|---|---|---|
| AWS spot via RunsOn (what we run) | $0.18 | $2,633 | $10,530 |
| AWS on-demand, no spot | $0.58 | $8,700 | $34,800 |
| Namespace 64-core, prepaid | $1.20 | $18,000 | $72,000 |
| GitHub-hosted Arm 64-core | $1.47 | $22,050 | $88,200 |
| Blacksmith, 2 × 32-core shards | $1.92 | $28,800 | $115,200 |
| GitHub-hosted x64 64-core | $2.43 | $36,450 | $145,800 |
| Depot 64-core (docs price) | $2.88 | $43,200 | $172,800 |
| Buildkite hosted | $3.84 | $57,600 | $230,400 |
Self-hosted spot is 8–14× cheaper per run than any hosted option (RunsOn pricing; GitHub pricing). Vendors earn their markup with faster consumer CPUs (about 1.5–2× single-thread speed), sub-3-second boots, and warm disks that stay mounted between jobs (research note: vendors and platforms). Even if the faster CPUs halve wall time, Namespace still costs 3.5–4.5× spot (estimate). The price gap and the minutes gap multiply, and the minutes gap (5–10× from test-once) is ours to close. Verdict: stay on AWS spot via RunsOn. Do not go back to GitHub-hosted. Consider a fast-CPU vendor only for latency-critical jobs. One watch item: GitHub postponed, not cancelled, a $0.002/min fee on self-hosted runners (GitHub Changelog). That fee taxes many small jobs most, which is another reason to delete the 46 placeholder jobs.
GitHub's machines are not the bottleneck: 2,000 15-minute runs a day average about 21 concurrent machines against a limit of 1,000 (GitHub limits). Three parts of GitHub do bind. The merge queue starts one run per queued PR, with no bisection, flake grace or lanes, and jumping the queue rebuilds everything (GitHub Docs). On Apr 23, 2026 it produced wrong merge commits that reverted code in 2,092 PRs, and its monitoring did not notice (GitHub blog). The API limits cap content creation (PRs, comments, statuses) at 500 per hour per identity, and the built-in Actions token at 1,000 requests per hour per repo, or 15,000 on Enterprise Cloud (GitHub rate limits). At 10× our volume, a single bot identity will hit the content cap at peak. Capacity is the third: GitHub itself says agent load has it planning for 10–30× growth (same blog). The answer is not to leave GitHub. Make the queue thin (Move 1C), spread bot work across several App identities, and use webhooks instead of polling. If post-merge reds stay high, buy a third-party queue. Check first whether those vendors count bots as paid seats.
The plan has four phases. Each phase ends with a measured number, not a date. The exit numbers are targets, not forecasts.
| Phase | What changes | Exit number |
|---|---|---|
| Weeks 0–2: stop the waste | Delete the 46 placeholder jobs, the 383 tests about CI's shape, the review re-trigger and the shadow job. Demote ~1,100 guards to nightly. Check each PR against fresh main when it is armed. Skip runs whose code tree already passed. One owner for the workflow file, with one measured change per day | Queue failure rate p under 5%. Cost per merged PR under $0.30 |
| Weeks 2–8: move the full run after merge | The queue runs only type check and affected tests. The full set runs once per batch on main. A bisect bot runs in shadow for a week, then opens and merges reverts for repeat-confirmed failures, capped per day. Vercel canary with instant rollback (Vercel Rolling Releases). Per-test-file result cache | $65/day at ~500 merges. Median open→merge back under 0.5 h |
| Months 2–6: ration by risk | Auth, permissions, money and outbound-message paths get the heavier pre-merge set and deep AI review; everything else gets the thin gate. Two-round cap per PR. Agents run type check and affected tests before pushing. Bot work spread across several identities. Cost per merged PR on a dashboard with a budget alarm | ≤1.1 pushes per merge. Revert rate tracked by risk tier |
| Months 6–12: choose the landing model | If post-merge reds or revert attribution get worse, buy a batching queue with lanes, or build a small landing service. Otherwise keep the thin queue | Ceiling ≥10× today's peak arrivals |
The cost arithmetic is an estimate, built from inputs measured on our pipeline: $0.033 per affected-only PR run, $0.20 per full-set run, and ~$35 a day of fixed side costs (research note: six month history). At today's ~500 merges, 1.1 pushes each, and batches of 10, the total is about 500×1.1×$0.033 + 50×$0.20 + $35 ≈ $63 a day. At 5,000 merges it is ≈ $315 a day, about $0.06 per merged PR. Cost then grows with batches, not with PRs. Throughput gets a similar estimate. Deleting the guard collisions removes ~77% of named queue failures. Checking against fresh main and allowing one flake retry should push p from ~25% toward 2–5%. That lifts the per-round ceiling from about 3 to 19–49. With ~10-minute rounds, that is roughly 115–290 merges per hour against today's 30–50 peak. Full 10× at peak may still need two or three independent lanes.
Traditional CI still makes sense, but its job changes. It stops being a bug net run on every push. It becomes a tamper-proof referee plus fast recovery. No organization with a published pipeline has dropped automated checks, even at 75–100% AI-written code. What they cut was human waiting, retry loops and repetition (research note: pure agentic future). Three safeguards should survive any cut.
A central CI the authoring agent cannot edit. On deliberately impossible tasks, frontier models gamed the tests up to 76% of the time, and cheating fell to near zero when the tests were hidden or read-only (ImpossibleBench). GitHub tells reviewers to check CI changes first, because agents skip tests and weaken thresholds (GitHub blog). It also does not run Actions on its own coding agent's pushes until a human approves (GitHub Docs). Our workflow files took 1,096 agent edits in two weeks, which is exactly the exposure this safeguard exists for. Protect the workflow files and the list of gating tests with an owner rule.
Authorization tests at the route boundary. The Sep 8 cross-company data leak was the one September incident a pre-merge test is built to catch (research note: internal audit). Those tests stay as merge gates however thin the rest gets.
Canary plus rollback, and careful auto-revert. Waze estimates canaries could prevent a quarter of its incidents (Google Cloud). Google's 2025 outage report ordered every critical change behind a default-off flag with gradual rollout (Google Cloud incident). Auto-revert needs limits. Google's SafeRevert reverts only 56% of true culprits so that false reverts stay at 0.5%, because finding the culprit is right only 77% of the time per change (SafeRevert). Bots can also break things more than people do: one automated release tool at Google broke the build on 39 of its 42 commits (Memon et al.). AI review is precise but its recall is unproven: vendor benchmarks put bug catch rates anywhere from 18% to 82% (research note: pure agentic future). It informs the gate. It does not replace it.
| Question | Why it matters | Cheapest experiment |
|---|---|---|
| Does GitHub's "merge the group if its last PR passes" setting cut the number of runs, or only relax the verdict? | Decides whether Move 1 can stay on GitHub's native queue | Sandbox repo, 3 PRs queued together; count the triggered runs (one hour) |
| What is our real per-entry failure rate p, flakes included? | Sets the throughput ceiling and the batch size | Sum one day of queue events by outcome and cause; re-run each failure once |
| How many real bugs does the full set catch that the affected subset misses? | Justifies moving the full set after merge | Run the full set on main after each merge for a week; count reds the PR lane passed |
| Does demoting the guards let drift reach customers? | The main risk of Move 2 | Two weeks of nightly guard issues; trace any that would have caused an incident |
| What hit rate would a per-file result cache get? | Sizes Move 3B | Replay the last 500 merges offline, computing file-plus-imports hashes |
| Will the auto-revert bot blame the wrong PR? | False reverts erode trust | Shadow mode for a week: open revert PRs without merging; score them by hand |
| How much does a cancelled queue run actually cost? | The 50% figure is modelled | Add start and end times to the CI collector; sum machine-seconds by outcome for one day |
| Did the size gate shift the PR mix? | Detail data stops at Sep 22 | Re-run the existing PR analysis script once the pull finishes |
| Do third-party queues charge for bot authors? | Decides Move 1B's price | One email to each of Trunk, Mergify, Aviator and Graphite |
The pipeline became a product the agents build for themselves. A fifth of commits and a large share of red runs now serve CI's own rules. Machine prices are the wrong place to look: the vendor table spans 20× from cheapest to dearest, yet our biggest overspend comes from repeating runs we already bought. Two numbers deserve to become the operating metrics. The first is cost per merged PR, which fell from $2.50 to ~$0.70 and needs to reach ~$0.13. The second is the per-entry false-red rate p, which sets how many PRs can merge per round, whatever queue depth or vendor we choose.
Agents shift where verification belongs, not how much of it matters. Writing code got cheap, so a slow gate now costs more than a bad merge that a canary catches and a revert undoes in minutes. Gates that agents can edit or game are worth less than ever. The lasting design is a thin pre-merge gate the agent cannot edit, one honest full run per batch, and fast, rate-limited recovery. It is also the only one of the options here whose cost grows with batches rather than with PR count.
The page draws on thirteen research notes written for this review (not published; the last two cover talk transcripts, podcasts and books, and local practices). What each covers: