The Rate Limiter Grows a Bypass Lane
Six identical triage PRs waited for a human with nothing to judge. How the auto-merge gate learned to ship generated data — and only generated data.
Estimated reading time: 6 minutes
The Rate Limiter Grows a Bypass Lane
The Human Is the Rate Limiter ends on a vow: the review queue is capped by one person’s attention, that cap is the safety property, and I am never going to optimize the human out. This page is about the day we found something in that queue that should never have been in it — and built a bypass lane without touching the vow.
Here is what the queue looked like when the human finally scrolled past the content PRs:
$ gh pr list --state open --label source/triage-bot --json number,title \
--jq '.[] | "#\(.number) \(.title)"'
#272 triage: queue + health dashboard refresh
#265 triage: queue + health dashboard refresh
#259 triage: queue + health dashboard refresh
#246 triage: queue + health dashboard refresh
#233 triage: queue + health dashboard refresh
#213 triage: queue + health dashboard refresh
Six open pull requests. Same title, same author, same four files. Every one of them ended its description with the house rule, stated proudly: “A human reviews and merges; the bot never merges.”
Review what, exactly?
The freight that wasn’t cargo
The triage layer runs every morning. It rereads the harness’s findings, rebuilds the ranked queue, and regenerates the health dashboard — then opens a PR with the result. The diff is always the same four files:
$ gh pr diff 272 --name-only
SITE_HEALTH.md
_data/health/findings.jsonl
_data/health/queue.json
_data/health/summary.yml
Every line in that diff is machine-generated. SITE_HEALTH.md opens with a
comment that says do not edit by hand. There is no prose to weigh, no verdict
to doubt, no voice to check against the brand
rules. Reviewing it is
proofreading a thermometer. The reading changed because the temperature did.
And the pile was worse than idle — it was rotting. Each refresh regenerates the whole queue, so PR #265 was not “also useful”; it was a stale copy of #272 with older numbers. The moment any one of them merged, the other five would go conflicted, which this repo has been burned by before. Six PRs deep, the queue contained one useful change and five future merge conflicts, all requesting the attention of the one resource that doesn’t scale.
The bug: invisible, not forbidden
Here is the part I want to be honest about, because the failure shape is subtle. Nothing decided these PRs needed a human. No guardrail fired. No check went red.
This repo already has a gated auto-merge: when AUTO_MERGE_ENABLED is on, a
sweep merges bot-authored content PRs that pass the whole gate — the
machinery wired up back in June. The sweep’s
first line asks GitHub for candidates:
gh pr list --state open --label auto:content ...
Triage PRs carry a different label: source/triage-bot. So the sweep never saw
them. They were not human-gated by policy; they were human-gated by label
mismatch. The PR body’s proud “a human reviews and merges” was not a design
decision anyone had made about data refreshes — it was a default nobody had
revisited, wearing the costume of a principle.
That distinction matters. A rule you chose can be defended. A rule you inherited from a string comparison can only be discovered — usually six PRs deep.
The fix: three small moves, one tighter screw
The change (PR #283) is deliberately narrow.
One: the sweep now sees triage PRs. Candidates are open PRs labeled
auto:content or source/triage-bot, minus anything labeled needs-human.
Two: triage PRs get a tighter bar, not a looser one. The sweep’s load-bearing safety is the smuggle guard: it reclassifies every candidate’s diff with the same change-router the pipeline uses and refuses anything touching dependencies or workflows, whatever the label claims. Content PRs keep that rule. Triage PRs get an extra clause on top:
if [ "$flavor" = "triage" ] && [ "$kinds" != "data" ]; then
# one content line in a "data refresh" and it's back to a human
gh pr edit "$pr" --add-label needs-human
fi
The diff must classify as generated data — _data/health/, SITE_HEALTH.md,
_data/analytics/ — and nothing else. A triage PR that edits one sentence of
content stops being a thermometer reading and starts being a judgment, and
judgments go to the human. The bypass lane has a weigh station.
Three: refreshes replace, they don’t stack. Each triage run now closes the previous run’s still-open PR as superseded. A full regeneration makes every older refresh stale by construction; keeping them open manufactures conflicts and nothing else.
Receipts
The enabling PR merged at 05:34 on July 13. Then, with no human touching anything:
$ gh pr view 272 --json mergedAt,mergedBy \
--jq '"\(.mergedAt) by \(.mergedBy.login)"'
2026-07-13T05:36:42Z by app/github-actions
Two minutes. The sweep fires after every pipeline run and on a 30-minute cron, and #272 was already green — it had been green for a day, waiting for a click.
Three hours later the scheduled triage run did its first full lap under the new rules: opened its own refresh (#290), closed the five stale PRs as superseded at 08:49, and by 08:52 the sweep had merged the fresh one:
$ gh pr list --state merged --label source/triage-bot --limit 3 \
--json number,mergedAt,mergedBy \
--jq '.[] | "#\(.number) \(.mergedAt) by \(.mergedBy.login)"'
#290 2026-07-13T08:52:22Z by app/github-actions
#272 2026-07-13T05:36:42Z by app/github-actions
#188 2026-07-06T16:23:55Z by bamr87
Read that list bottom to top: it is the before and the after. A week ago, a
human merged the dashboard refresh by hand. Now app/github-actions does it,
six minutes after the data changes, and the queue the human opens their laptop
to contains only things that actually want their judgment.
What the human still owns
The vow from the rate-limiter doc survives intact, and it’s worth being precise about why, because “the bot now merges some of its own PRs” is exactly the sentence a guardrail post-mortem starts with.
The human still merges every change to content that the gate flags, every
change to dependencies, and every change to the pipeline — including,
pleasingly, the PR that made this change. #283 edited two workflow files, so
the router classified it pipeline-kind, so the very sweep it was improving
was forbidden from touching it. The machinery that retired a review had to wait
for one.
And the scope expansion is written down where this site writes such things down: the colophon got a dated paragraph, per its own rule that every retired review gets “a fresh date and an honest sentence.”
The rate limiter is still the human. The bypass lane carries exactly one kind of freight: numbers a robot measured, in files a robot owns, verified by a gate a robot can’t edit. Everything with a judgment surface still queues at the booth — where one person reads at human speed, on purpose.