A few months ago, an engineer asked me if we could batch-migrate all our internal repos to use gen2 executors. The old way would have meant someone opening over a hundred pull requests by hand, one repo at a time, then chasing teams to merge them. Instead, I wrote a prompt, triggered a pipeline, went back to my other work, and by the next day there were 150 open PRs across the org with the change already made, reviewed by me, and ready for each team to merge at their own pace.
The pipeline is still running.
When new repos appear, they get fixed too.
That pipeline is the repo fixer. Here is how it works and why I think the pattern is worth stealing.
Hypothesis
Every codebase accumulates patterns that made sense at the time and don’t anymore later on. Hardcoded image tags. Old resource classes. Config patterns that predate a better standard. Everyone knows these should be cleaned up. Nobody does it because the cost is too high: you have to touch every repo, write the same PR description forty times, and then wait for forty teams to review something trivial.
My hypothesis was that LLMs would be good enough at narrow, targeted transformations to make this class of work economically viable again. Not “write me an application” prompts. Specific, bounded prompts: “find config files that reference this executor, update them to use this one instead, explain what you changed and why.” If that worked, and if I made it easy enough for other engineers to define their own fixers, people would use it without being asked.
Both turned out to be true.
Setup
My process always reflects a focus on “the customer”; in this case the customers were my fellow teammates, divided into 2 distinct groups:
Customer 1: The SME who knows the fix needs to happen but doesn’t want to make the PRs. They write the prompt once, defining what the broken pattern looks like and how to fix it. That’s the end of their work. They never touch a repo directly.
Customer 2: The engineer responsible for actually merging the fix in their repo. They get a well-described draft PR from a bot user, can review it quickly, and merge on their own schedule without being chased.
I consciously built for both from the start.
Repo fixer is a CircleCI pipeline that runs on a schedule across every repo in the org. The engineer who knows a fix needs to happen writes a fixer once. After that, the pipeline handles everything.
Each fixer has two phases.
The first phase is detection. Before spending money on an LLM, the pipeline checks whether this repo even needs the fix. This is done with cheap tools: bash, grep, or a code search query. If the pattern isn’t present, the fixer exits immediately. Most repos exit here. You only pay for the LLM on the repos that actually need work.
The second phase is the fix itself. If detection passes, the pipeline runs the fixer, which is defined as a bash script, an LLM prompt, or a combination of both. The prompt is written by the person who knows the codebase pattern best, so it’s specific. When the gen2 migration ran, the prompt knew exactly which executor names to look for, what to replace them with, and what edge cases to watch for.
It’s worth noting that the gen2 fixer used an LLM prompt, but it could have been written with ast-grep or bash tools with more work. LLMs handle edge cases you didn’t even think of very well and I knew that it would only run once because of the gating detections, so I decided that LLM prompt was the way to go here. I could have spent hours finding and tuning the script to work on all the edge cases but… I didn’t have to.
When the fixer decides to make a change, it commits the diff and opens a PR as a bot user. Using a bot user means any engineer on the team can approve and merge the PR. If the author opened it, they couldn’t merge their own work, which would make the whole point moot.
The PR includes everything a reviewer needs: what changed, why, which fixer generated it, and a link to context. PRs open as drafts by default. I review them in bulk before they go out, then pass them off to each team. That review gate is not optional. When you’re opening hundreds of PRs at once, you need to QA the output before creating noise for everyone downstream.
There are also two Claude skills for managing the system day-to-day: one to trigger a fixer immediately instead of waiting for the next scheduled run, and one to view and triage the PRs that are waiting to be merged.
Personally, I’m not a “wait to act until it’s perfect” person. I’m a “get something working and then iterate” person. So the tooling itself went through several iterations.
The first version was a pile of bash scripts. Moving it to a Go binary made a real difference: writing a new fixer became a much better experience for the subject matter expert, which is the whole point. If defining a fixer is painful, people won’t write them. The PR descriptions also evolved. The first version shipped with an empty description. It now includes a disclosure that the change was generated by a tool, signed commits, and enough context that a reviewer can approve it in under a minute.
The inspiration for the pattern was Sourcegraph batch changes. The key difference is that repo fixer keeps running on a schedule forever. Batch changes are a one-time operation. Patterns keep appearing in new repos, so a one-time run puts you back where you started six months later.
Results
The cleaner result is behavioral. I expected to have to suggest the repo fixer to people. For the first few fixers, I did. Now engineers come to me asking to add one. The SME who knows a pattern needs fixing writes a prompt, and their work is done. The PRs appear on a schedule without them doing anything else.
One thing that caught me off guard: not all of our repos had the same default branch. The pipeline had to handle that variation, which wasn’t something I had thought through on the first pass. It’s worth building for early if you’re doing this across a large org.
TL;DR
The LLM is not the interesting part of this. The interesting part is the structure around it.
A detection pass before the LLM call is not optional at scale. If you skip it and run the prompt on every repo unconditionally, you burn money and slow everything down. The cheap filter is what makes the system viable to run on a schedule forever.
Draft PRs with a review gate before merge are also not optional. LLM-generated PRs at volume create a lot of noise. You need a way to verify the output before it lands in forty engineers’ review queues.
The bot user pattern solves another big problem. If a human opens a PR, that human is now in the critical path for every merge. A bot user removes that dependency entirely. Anyone can approve. Teams merge on their own schedule.
And the scheduling matters more than I expected. The value of repo fixer is the tenth run, six months later, when a new repo appears with the old pattern and gets a PR the next morning without anyone having to remember to file a ticket.




