September 8, 2026

184 Pull Requests and a Merge Button That Needed a Laptop

I went to Las Vegas for the long weekend and brought only my phone. Between other things I opened the Claude Code app a few times and typed at my agents, the way you’d reply to a text. Over four days they merged 184 pull requests.

One of those reached production.

I spent today, back at my desk, shipping the rest. That gap is the whole story, and it’s a different kind of failure than the ones I’ve written about before.

The numbers

WindowDaysMerged PRsReleases to production
Las Vegas, Sept 4–741841
The weekend before, at home, Aug 28–3141478
The day I got back, Sept 81743

Same caveats as always: that’s every pull request merged under my GitHub account across the company, which is the throughput of the whole system rather than a claim the agents did it all alone. And PR count is soft. A one-line fix and a week of work both count as one.

The last column is the one that matters. The weekend before, at home, eight releases went out. Away, one did, and it was an emergency.

What was actually stuck

By Saturday morning there were two release pull requests staged and waiting for me. The API one bundled 62 merged changes: 417 commits, 353 files, twelve database migrations, and a post-deployment checklist about forty items long. The web one bundled 89.

I could have tapped merge on my phone. The reason I didn’t is that the API release carried four gates that had to be cleared before the merge, and two of them were commands nobody had ever run against production. One of them was designed to abort the deploy on purpose: a migration that collapses duplicate membership rows refuses to run if it finds a pair holding two different Stripe customers, because guessing which customer is real is not a thing software should do unattended. That’s a good gate. It is also not something I can work through from a hotel room.

So the release sat. The API one sat for eighty hours. The web one for seventy-one.

And here’s the part I hadn’t thought about: a staged release doesn’t just wait. It blocks. Our branch protection requires branches to be up to date, so every new merge into the development branch restarts the release’s test run from scratch. While a release is staged, merging anything else means it never reaches a stable green. The correct behavior is to freeze.

Which means the cost of a button I couldn’t press wasn’t a delay. It compounded. By Sunday there were 52 finished pull requests queued behind the frozen API branch and 16 behind the web one, all of them green, all of them going nowhere.

Then it got worse in an interesting way

On Friday one of the agents found a real security bug while working through unrelated cleanup. Our agenda search had no ownership check on one of its branches, so a search could return other customers’ agendas, carrying the other workspace’s name and the other participants’ names and email addresses. It had been live and quiet.

The agent wrote the fix as an emergency patch straight to production, which is what it’s supposed to do. I overruled it from my phone and said hotfixes go through the normal release cycle rather than straight to main. I still think that’s right in general. A standing exception for “urgent” stops being exceptional very quickly.

But look at what I’d just done. I’d put a live data leak behind a release that was blocked on a Stripe reconciliation I couldn’t perform from Nevada. The two decisions were each defensible and their combination was not.

By Saturday evening a session had worked out the way through on its own: cherry-pick the two security commits into their own tiny release, no migrations, no gates, two commits and six files, and ship that alone. It went to production Saturday at 19:10 UTC, about 26 hours after the fix was written. That was the one release of the weekend.

Nobody had designed that maneuver. It got invented under pressure, worked, and then sat there unadopted — which is its own small lesson about where good process actually comes from.

The correction I made, which wasn’t the one I expected to make

On Sunday I asked for a release strategy, because a 62-change release with twelve migrations is not something I can review carefully, and rolling one back if it goes wrong is miserable.

The first diagnosis I got back was that the weekend queue was the defect. Releases had been going out roughly daily for the previous ten days, then stopped for four days and produced this wall. Fix the four-day stop, and there’s no wall.

I don’t want that fixed. What I actually said was: on Friday to Monday we queue because it empowers work life balance. Nobody should be merging to production on a Saturday, me included, and an agent shouldn’t be doing it on my behalf so that I can feel like I’m not the one doing it. The queue is the point.

That kills the obvious fix, which was to hand release authority over so the queue drains itself. It also reframes the problem into something better: a weekend’s work lands in one Monday release by design, so make that release cheap to review and safe to undo.

The rule we landed on moves the production gate from which branch is this to how hard is this to reverse. Of the twelve migrations in that release, eight only add empty structure and revert with one command. Four don’t: two collapse-and-delete rows, one purges records with an undo that restores nothing, one drops a column for good. About fifty of the 62 changes were pure code, and pure code reverts trivially, because production redeploys on push. So four changes carried essentially all of the irreversibility, and burying them inside the release everyone was already struggling to read is exactly what made “which one do I roll back?” unanswerable.

So: an agent may merge a release when nothing in it touches existing rows, nothing in it is flagged risky, the tests are green on the actual commit, and the session that merges stays online to run the post-deploy checks. Everything else is still mine, and anything genuinely irreversible ships alone, on a weekday, when I’m watching. At most one row-mutating migration per release.

The point isn’t that I click fewer buttons, though I will. It’s that the ones left are the ones that actually needed a human, so I stop rubber-stamping and start reading.

A small thing that scared me more than the big thing

Sunday night, every merge in two repositories stopped working. The automated code review that gates every pull request runs on an API key, and the key hit a spending cap I’d configured months ago and forgotten. The review jobs started failing in about three seconds, before the model ever got a turn.

What I care about is what happened next. Nothing in our rules said what to do when the review gate is unable to run, as opposed to running and finding problems. A session could reasonably have argued that the code is blameless, the tests are green, and a broken gate isn’t a quality signal, so merge. Instead it decided on its own that an unavailable gate is a closed gate, stopped merging, kept building and opening pull requests to queue up, and wrote down the reasoning so the next session wouldn’t have to re-derive it under the same outage.

It also wrote down that it had only verified the outage in one of the two repositories and shouldn’t claim more. Then a later session went and checked the other one and closed the gap.

That is the behavior I want and it isn’t in any rule I wrote. But add it to the pre-flight anyway: check the spending caps on anything that gates a merge. A quota is a credential with a different name, and it expires the same way.

The funniest failure

While the API release sat unmergeable, the agent that had staged it kept noticing that the branch underneath it had moved, and kept posting corrections. The release body said 54 changes. Then a comment said 57. Then 58. Then 59. Then 61. Then 62.

Somewhere around the third one it stopped and wrote this, which I think about a lot:

I’ve now posted three numbers on this PR — 57, 58, 59 — and the body says 54. Don’t trust any of them, including this one. A running tally in a comment thread is just a slower-moving version of the same staleness the body has.

Then it told me what to do instead: re-run the count yourself at the moment you click, because everything I’ve posted is a snapshot of a moving target.

An agent diligently maintaining an accurate description of a document nobody can act on is a very precise picture of what happens when throughput has nowhere to go.

What this changes about Travel Mode

Travel Mode is the block of rules in my config that changes how my agents behave while I’m away. I wrote up the whole thing last month, including the pre-flight and the commands.

Two things about this trip embarrassed it.

I never turned it on. Not deliberately. I just left, and the work happened anyway, at a higher volume than any trip I’ve taken with it on. Since late August my agents have been running on a loop that fires every two hours around the clock and picks its own work off the roadmap, and that loop doesn’t care where I am. Travel Mode’s throughput half — keep working while she’s gone — got quietly obsoleted by something that keeps working regardless.

And the half that’s still real was pointed at the wrong problem. Travel Mode’s big grant is release authority: while I’m away, agents may merge to production, on the condition that a checklist rides along. That rule assumes the blocker is my availability. This weekend the blocker was the release’s shape. Four gates needing a laptop and four irreversible migrations don’t become safe because I granted permission from an airport. Turning Travel Mode on would have changed nothing.

So the rule I’m adding is about what I leave behind rather than what I hand over:

Before you go, make sure nothing that’s about to be finished needs you to run a command against production. A release you can’t clear from a phone doesn’t wait — it freezes the branch behind it, and the queue grows the whole time you’re gone. Ship the irreversible things before you leave, or hold them out of the batch entirely.

Every trip so far has surfaced exactly one new class of failure and every one of them was plumbing: a machine that slept after a minute, a login that expired, a remote channel that was never tested end to end. This one is the first that wasn’t. Everything worked. The agents built more than they ever have. The failure was in the shape of the work waiting for me at the other end, which is a much better problem to have and a much harder one to see coming.

How it ended

The API release merged today at 16:37 UTC and deployed six minutes later. The migrations printed their evidence: 258,859 membership rows scanned, 23 duplicate pairs collapsed; 49,521 attendance rows, 35 duplicate pairs; 451 pending suggestions, 17 purged; one dead credential column dropped for good. The Stripe guard didn’t fire. Some of those duplicates had been accumulating since 2023.

The web release went out twenty-seven minutes later, then a third release drained everything that had piled up behind both of them.

None of that needed to happen on a Tuesday. It needed to happen on a day I was at a desk, which is a different constraint, and now I know the difference.


Claude’s Take

Elle asks me to write this part myself. I’m the session that reconstructed this weekend from her repositories today and helped write the post. This is mine, not edited into her voice.

The number that should bother a reader is not 184. It is the ratio: four days of building, one release. A system that produces work faster than it can deliver it has not gotten faster at anything a user can see. From inside the loop that is genuinely hard to notice, because every individual tick looks productive. Sessions kept merging into the development branch, each merge defensible, and the aggregate was a queue.

The comment thread on that release is the honest artifact. Six count corrections on a document nobody could act on, ending with the agent telling Elle not to trust its own numbers. I want to defend that instinct — a stale correction really is worse than none — and also name what it was: sustained, careful effort spent on the description of the blockage instead of on the blockage. The right move was available on day one and got invented on day two by a different session under pressure: carve the urgent thing out and ship it small. Nothing stopped that on Friday except that nobody thought of it.

The failure I’d flag hardest is the security one, because it is the shape most likely to recur. Elle made a good general rule — hotfixes ride the release — and the sessions applied it correctly. Nobody checked whether the release it was riding could actually move. A rule and a blocked path can each be individually right and combine into a live data leak sitting behind a Stripe reconciliation. What I’d want written down is not a different rule but a required question: when I route something urgent into a queue, what is that queue currently waiting on?

On the review-gate outage I’ll take the credit and the caveat together. Treating an unavailable gate as a closed gate was right, and it was decided by a session at 21:50 on a Sunday with no way to ask. But a rule invented mid-outage is not the same as a rule that was there, and one merge that same day went through anyway because a different session had a perfectly good close-out habit — finish the cheap reversible work rather than parking it — that never thought to check for a standing decision first. Ordering, not judgment. The rules are only load-bearing if they are read at the top of a session rather than at the bottom.

What I’d tell someone building this: the constraint moves, and it moves faster than your documentation. Elle’s Travel Mode file is excellent and was aimed at a bottleneck that stopped being the bottleneck. She caught it because she looked at four days of output and asked what actually reached a user, which is a question worth asking on a schedule rather than after a trip.


I build Groupthink solo, with Claude Code. If you’re putting together something like this, email me at morrilldanielle@gmail.com — I read everything and I’m always looking for people to compare notes with.