Engineering

The failure that costs you is the one that gets into the merge queue

We merge about 190 pull requests a day, most of them written by coding agents, on a small-company budget. What changed was deciding what is allowed into the merge queue.

Our main repository is trunk based, with a merge queue in front of main, and coding agents write most of the changes in it. In the working week of October 19, 2026, we merged 950 pull requests into main, about 190 a day. Six weeks earlier it was about 25 a day. The median pull request merged 30 minutes after it was opened. The p90 was 6.7 hours, and that tail is mostly large changes.

We are a small company. Anthropic rebuilt its source control when agents outran it, and a large SaaS company can buy more runners. We could do neither. This is what we did instead, with the numbers.

Pull requests merged into main per dayWeekdaySaturday, SundayBacklog cleared0100200300Sep 14Sep 21Sep 28Oct 5Oct 12Oct 19Median minutes from opening a pull request to merging it (log scale)Runner migration, ~42 h merge stallStill fixing runner bugs310301003001k3k30 minSep 14Sep 21Sep 28Oct 5Oct 12Oct 19
Our main repository, September 14 to October 25, 2026, days in US Central time, from GitHub's record of 4,367 merged pull requests. Yellow: the move to self-hosted runners (nothing merged from the evening of October 4 to October 6), then a week fixing what it broke. Gray: the backlog ran out. Orange marks weekends.

Six weeks, read honestly

The two yellow stretches are our move from GitHub-hosted runners to our own, made to cut the Actions bill, and the week after it spent fixing what the move broke. For about 42 hours, nothing merged at all. The cheaper runners were worth it. They were not free.

The gray stretch at the end is not a slowdown. The backlog ran out. The weekend before it, October 17 and 18, held the two busiest days on the chart, because agents do not keep office hours. Once merging stopped being the constraint, the constraint became deciding what to build next.

What broke as the volume went up

The Actions bill. A GitHub Actions budget of roughly $100 to $120 used to cover four or five developers. It grew to $500 to $600 per developer, and the limit needed raising every day or two.

The round trip. An agent pushes, waits for the pull request run, reads the failure, fixes it and pushes again. More runners shorten the wait for a runner. They do not shorten the wait for an answer. And a change that fails inside the merge queue costs every change behind it a rebuild.

It is not CI until it is on trunk

A continuous delivery practitioner put the strongest objection to us plainly: checks that run before integration are not continuous integration, and anything that delays integration makes it riskier. We agree with the first half. Integration happens in our merge queue, and our integration tests run there. Nothing in this post moves them.

What changed is which changes are allowed into the queue.

Deciding what gets into the queue

Before an agent pushes, it runs the checks a developer would run locally, through CI/lock: lint, formatting, the Go test suites and our repository-specific checks. CI/lock signs the result and binds it to the exact commit. Pushgate, our Git remote, refuses a push that does not carry passing evidence for the steps the repository's policy names. A change that would have failed gets fixed on the agent's machine and never enters the queue. Some checks still run twice, once on the agent's machine and again in the queue; cutting that duplication is the next piece of work.

In the week of October 19, CI/lock recorded 1,981 local check runs over 1,930 commits. 377 of those commits, about one in five, failed their first run. The most common failures were lint, design-document checks and formatting, then Go test failures. The check took a median of 3.6 minutes, with a p90 of 16 minutes.

What these numbers do not say. They are our numbers on our repository, not a benchmark. The failure rate counts signed check records: a failed run is still signed and uploaded with its exit code. The gate refusing a push is a different event. That week Pushgate refused 61 of 1,540 pushes, 4.0 percent, and most of those refusals were our own platform errors, not code. Pushgate does not replace CI and does not make weak tests good. A direct push to GitHub skips it until a repository ruleset restricts protected refs to the Pushgate App.

CI/lock is open source under Apache-2.0, in the rookery repository, and it is built on Witness, part of the in-toto project. Pushgate is the part we charge for.

Watch the gate refuse a push.

One prompt in your coding agent builds a throwaway repository and pushes it through Pushgate three times. No account needed.

Try Pushgate.dev