Fleet-Wide Maintenance: Generating Twenty Pull Requests Is the Easy Part
Twenty correct branches, each with a green build, all still open three weeks later. The initiative produced negative value.
Where agents genuinely change what is possible
Most claims about agent productivity are arguable. This one is not: bumping a dependency across twenty repositories, applying a deprecation fix everywhere, or migrating every service off an API that is going away is work that used to be a quarter-long initiative nobody staffed, and it is now a scheduled job.
It is a good fit for a specific reason. Each repository's change is small, mechanical, and independently verifiable by a test suite that already exists. That is exactly the profile that hands off well, multiplied by twenty.
So the interesting question is not whether it works. It is what breaks when it does, and the answer is not what people expect.
The first failure: twenty pull requests and nobody to merge them
The generation succeeds. Twenty branches appear, each correct, each with a green build. They then sit, because every one needs an owner to look at it, and the twenty repositories have somewhere between four and fifteen different owners, none of whom asked for this today.
Three weeks later half are still open, they have conflicts, and the dependency has released two more versions. The initiative has produced negative value: real work for other teams, no completed migration, and a set of stale branches that now need rebasing before anyone can evaluate them.
This is the constraint from parallel agents and the serial reviewer in its most acute form, because here the reviewers are not even the people who started the work.
Get the merge commitment before you generate
The fix is organisational and it goes first, before any code. Know who will merge each pull request and when, before opening any of them. That conversation takes an afternoon and it is the difference between a completed migration and twenty abandoned branches.
In practice it usually means going in waves aligned to teams rather than opening everything at once: five repositories this week whose owners have agreed, five next week. Slower on paper, and the only version that finishes.
The second failure: the change is not the same change
"Bump library X from 3 to 4 in twenty repositories" sounds like one change applied twenty times. It is usually three or four different changes wearing the same description.
Twelve repositories use the library in the straightforward way and the bump is a lockfile edit. Five wrap it in an adapter, so the change is in the adapter and the tests around it. Two use a deprecated entry point that version 4 removed, which is a real migration. And one has a patched fork nobody remembers creating.
An agent told to apply the same change everywhere will produce something plausible in all twenty, and the four unusual ones are where the bugs are — passing tests included, because a test suite that never exercised the deprecated path will not notice.
Classify before you batch
The step that prevents this is a survey pass that changes nothing.
#!/usr/bin/env bash
# Read-only. Classify each repo before any change is proposed.
set -euo pipefail
LIB="${1:?library name}"
for repo in $(cat repos.txt); do
d=".work/$repo"
[ -d "$d" ] || git clone -q --depth 1 "git@host:$repo" "$d"
ver=$(jq -r --arg l "$LIB" '.dependencies[$l] // .devDependencies[$l] // "-"' \
"$d/package.json" 2>/dev/null || echo "-")
sites=$(rg -c --no-messages "from '$LIB'|require\('$LIB'\)" "$d/src" | wc -l)
wrapper=$(rg -l --no-messages "$LIB" "$d/src" | rg -c 'adapter|wrapper|client' || echo 0)
patched=$([ -d "$d/patches" ] && ls "$d/patches" | rg -c "$LIB" || echo 0)
printf '%-28s ver=%-10s sites=%-4s wrapper=%-3s patched=%s\n' \
"$repo" "$ver" "$sites" "$wrapper" "$patched"
done
Ten minutes of output, and it sorts twenty repositories into the twelve that are genuinely identical, the five that need a variant, and the three that need a person. That sorting is the entire difference between a batch that works and one that quietly ships four wrong changes.
Pilot on one, then three, then the rest
Never open twenty first. Do one repository end to end, including the review and the merge, and see what the reviewer says. Their comments are information about the brief, and applying them before generating nineteen more is free.
Then three, ideally from different classes. Then the remainder. Each stage is a chance to discover that the change needs an adjustment, at a cost of one or three rather than twenty.
Order by consequence, lowest first
Sequence the rollout so the least critical repositories go first. An internal tool, a documentation site, a service with no external consumers. Then the middle tier. The payment service goes last, when the change has already survived contact with fifteen other codebases.
This is a deployment practice applied to a migration, and it works for the same reason: the early repositories are a canary, and the information they produce is worth more than the hours saved by doing everything at once.
Verification has to be per-repository
A green build in each repository is necessary and not sufficient, because the interesting failures are in repositories whose tests never covered the affected path.
So the survey should also record test coverage of the call sites it found, and a repository with call sites and no coverage of them is flagged for human verification rather than accepted on a green build. That is usually three or four of the twenty, and it is a manageable amount of manual checking once you know which ones.
For a batch change specifically, ask what would have failed if the change were wrong. If the answer is "nothing, that path is not tested", the green build is telling you about the test suite rather than about the change.
What each pull request should carry
Reviewers seeing an unrequested pull request from an automated process need context immediately: why this is happening, who asked, what class this repository was put in, what the agent verified, and what it wants the reviewer to check specifically.
The last part matters most and is usually omitted. "This repository wraps
the library in src/clients/adapter.ts; please confirm the
adapter's retry behaviour is unchanged" gives the reviewer a two-minute job.
Without it they either rubber-stamp or spend twenty minutes working out what
they are supposed to be looking at, and most will do the first.
Major versions need a human to read the changelog
A patch bump is mechanical. A major version bump involves reading release notes, understanding what the breaking changes mean for your usage, and deciding whether an intentional behaviour change is acceptable.
An agent can summarise a changelog well and cannot make that judgement, because it turns on how your systems use the library and what your users depend on. The workable split: agents handle patch and minor bumps automatically, and a major version produces a summary and a proposed plan for a person to approve before anything is generated.
Sometimes the batch is telling you something
Worth pausing on when a change is genuinely identical across twenty repositories. That is a strong signal that the thing being changed should be in one place: a shared library, a template, a base configuration.
The agent has made a symptom cheap to treat, and cheap treatment reduces the pressure to fix the underlying duplication. That is a real risk of this capability — it makes fragmentation sustainable, and sustainable fragmentation persists. If you find yourself running the same batch quarterly, the batch is not the solution, it is the workaround.
Standing job or one-off
Both exist and they need different treatment. A one-off migration is a project with an end, and it deserves the classification and pilot process above.
A standing job — weekly dependency updates, say — is infrastructure, and its main risk is volume fatigue: twenty small pull requests a week become background noise, and background noise gets approved without reading. Batch the routine ones by repository rather than by dependency, keep the frequency low enough that each batch is an event, and separate the security updates so they are visibly different from the housekeeping.
What to measure
Not pull requests opened. The number that matters is repositories actually migrated, and the gap between the two is where every failure in this article shows up.
Track opened, merged, and time from open to merge. If merges lag opens by more than a week, the constraint is review and generating more is actively harmful. If a repository has had an open bump for a month, it is not going to be merged by waiting, and someone needs to go and talk to its owner — which was the conversation that should have happened before the branch existed.
The repositories nobody owns
Every fleet has some. A service whose team was reorganised, an internal tool with one original author who left, a library four teams depend on and none maintain. They are the repositories most likely to be running an old dependency and least likely to have anyone who will merge a pull request.
A batch initiative surfaces them, which is useful, and then stalls on them, which is not. The productive response is to treat ownership as a prerequisite rather than a detail: repositories without an owner are excluded from the batch and escalated as an ownership question, not left as four permanently open branches that make the migration look nearly finished.
Rollback across twenty repositories
Worth thinking about before you need it. If the bump turns out to be bad — a regression that only appears under production load, in one service, three days later — what does reverting look like across everything already merged?
If each repository got its own single-purpose commit, the answer is twenty reverts, which is tedious and tractable. If the change was mixed with other work, or squashed into a release commit, it is considerably worse. Insisting that a batch change lands as an isolated, individually revertible commit costs nothing at the time and is the entire recovery plan.
What it feels like when it works
A survey on Monday sorting twenty repositories into three classes. One pilot merged on Tuesday with two review comments that improve the brief. Three more on Wednesday. The remaining twelve opened Thursday against owners who already agreed to look, merged over the following week. Three flagged for human verification because their tests do not cover the call sites. Two excluded because nobody owns them, raised separately.
Two weeks, one person, twenty repositories actually migrated. The version that fails takes the same two weeks, opens all twenty on the first afternoon, and finishes with eleven merged and nine rotting.
One organisational note to close on. The person running a fleet-wide batch is usually not on the teams receiving it, which means the whole exercise depends on goodwill they are spending rather than authority they have. Spending it well — small batches, clear asks, a specific thing for each reviewer to check, and finishing what you start — is what makes the second migration easier than the first. Spending it badly gets your automated pull requests filtered.
The version of this that is genuinely new
Worth ending on, because the hazards above should not obscure it. Before this capability existed, a cross-cutting fix in twenty repositories simply did not happen unless it was urgent enough to staff a project. The work was known, agreed to be worthwhile, and permanently deferred.
That category — worth doing, never worth staffing — is where the real gain is, and it is larger than the time saved on any individual change. A team that can clear it routinely accumulates less of the drift that makes old codebases expensive, and that compounds in a way no single migration does.
Fleet-wide maintenance is where agents genuinely change what a small team can attempt, and generation is not the constraint — merging is. Secure the merge commitment from each repository's owner before opening anything. Survey first with a read-only pass, because "the same change" is usually three different changes and the odd ones out are where the bugs are. Pilot on one, then three; order the rollout from least critical to most; flag repositories whose tests do not cover the changed call sites; and measure repositories migrated, not pull requests opened.