AI Will Not Pay Down Your Tech Debt. It Will Multiply It.
How we stopped scheduling debt sprints and started merging debt fixes inside the normal review queue.
The short version: stop treating tech debt as a project you schedule and start treating it as a stream you automate. Have scanners surface candidates on a fixed cadence, have a coding agent open the pull request with the fix already written, and let developers review and merge inside the queue they are already working in. Then judge the whole thing on two numbers, the merge rate of those pull requests and what production reliability actually does. Ticket counts will lie to you. Those two will not.
That is the method. I argued the general case for this a while back in Tech Debt Explained: What It Really Costs You, and the argument has held up. What has changed is that I now have numbers from running it, including some that did not go the way I wanted. The reason it matters more this year than last is the part most leaders still have backwards.
The assumption that is about to break
The common belief is that AI coding tools will grind down technical debt over time, because faster developers can finally get to the cleanup they never had time for.
The data says the opposite is happening. GitClear and GitKraken analyzed 623 million code changes between 2023 and 2026 and found block duplication up 81%, refactoring "moved" lines down 70%, legacy maintenance down 74%, and a 47% increase in code that catches errors without evaluating why they happened (The Maintainability Gap). Cross-file function calls, a decent proxy for reuse, fell 35%. Their earlier report tracked the same trend from its start: the refactored share of changed lines dropped from 25% in 2021 to under 10% in 2024 while copy/paste climbed from 8.3% to 12.3% (2025 AI Copilot Code Quality).
If you want the peer-reviewed version rather than the vendor version, there is now a large-scale study of 302,600 verified AI-authored commits across 6,299 GitHub repositories pointed at the same question.
None of this means the tools are bad. It means the incentive is obvious. Writing new code is now nearly free. Understanding existing code and restructuring it is still expensive, because it costs context, attention, and time that no model shortcuts for you. So the cheap thing scales and the expensive thing does not. More code reaches production, the surface area grows, and debt accumulates faster than any quarterly cleanup can absorb.
The corollary is uncomfortable: if generation is automated and remediation is not, the gap between them widens every sprint. The only response that scales is to automate remediation too.
Why the debt sprint keeps losing
Most organizations still park debt work in blocks. A quarter gets a cleanup allocation, a team gets assigned, and the work goes on a roadmap next to features.
That model loses every time it meets a real roadmap. A customer commitment lands, the debt block slides, and by the next planning cycle the items have grown large enough that nobody wants to start them. What is left shows up later as slower release cycles and more incidents.
The cost is not theoretical. A longitudinal study of 43 developers found they waste on average 23% of their development time on technical debt, and that they are frequently forced to introduce new debt in order to work around debt that already exists (Besker, Martini and Bosch, TechDebt '18). Broader estimates put the range at 23 to 42 percent of developer time, which lines up with the 25 to 40% of developer capacity I have seen quoted across the industry. Separate work has started connecting debt directly to lead time on real issues, which is one of the four metrics your leadership already looks at.
Three patterns make it worse, and I have watched all three up close. I first sketched this in Engineering Productivity Unleashed back in early 2025, before I had tried to run it at scale:
- Waiting for pain. By the time debt hurts enough to prioritize, the fix is large and risky, which makes it easier to defer again.
- Assigning debt only to seniors. It concentrates the work on the people who are already the bottleneck and teaches juniors that cleanup is somebody else's job.
- Counting closed tickets. You can close a hundred debt tickets and watch incident rates climb, because nothing forced you to check whether the items you closed were the ones causing incidents.
What we actually built
For context on scale: roughly 600 engineers, more than 1,000 services, and a stack spread across TypeScript, JavaScript, Java and Python. At that size you do not have a tech debt problem, you have a tech debt distribution problem. No central team can see it, and no single owning team can see enough of it to prioritize well. So the first thing we built was not the automation. It was a dashboard that made debt visible per service, because you cannot route work you cannot see.
Then we inverted the flow. Instead of a human finding debt and then writing the fix, a scheduled job finds candidates and a coding agent opens a pull request with a proposed change already in it. This is the shift I described in From Writing Code to Leading Agents, applied to the least glamorous work in the building. The developer's first contact with the item is a diff in their normal review queue, not a ticket in a backlog they will never open.
The loop:
- Scan on a fixed cadence, into the dashboard. Weekly worked for us. Less often and the queue grows stale, more often and you flood people. The scanner surfaces a bounded number of candidates per service rather than everything it can find, and the dashboard is what turns "1,000 services have debt" into "these eleven services have debt that is currently costing you incidents."
- Ground the agent in your conventions, do not "train" it. This distinction matters and most write-ups get it wrong. We are not fine-tuning a model on the repo. We are giving it context: retrieval over the relevant modules, the team's convention files (
CLAUDE.md,AGENTS.md, editor rule files), existing lint and codemod configs as hard guardrails, and access to internal docs. A generic model proposes changes that fight the architecture. A grounded one proposes changes that look like the surrounding files, which is most of why review stays short. - Open the PR with the risk stated. Not "refactor utils.py" but which failure mode this reduces, and which service it touches. Reviewers triage on risk, so give them risk.
- Route it into the existing rotation. No separate meeting, no separate sprint, no separate board. The moment debt work needs its own ceremony, it starts competing with features again and you know how that ends.
- Measure merge rate inside a fixed window. We used two weeks. This is the health check for the whole system, and I will come back to it.

The first ruleset: stale feature flags
Do not start with something subjective. Start with debt that is mechanically provable, and feature flags are the cleanest example there is.
A flag that has been at 100% on for months is not a flag. It is a permanent branch in your code that nobody has deleted, plus a dead alternate path, plus tests maintaining that dead path. A flag at 100% off is worse, because it is code that ships to production and never executes. The dashboard identifies both from the flag service's own rollout state, which means the signal comes from runtime reality rather than from someone's judgment about code quality. From there the agent opens a pull request that deletes the flag check, collapses the surviving branch, removes the code that just became unreachable, and drops the tests that only covered the dead path.
Three reasons this is the right first rule at any real scale:
- The evidence is objective. There is no arguing about whether a flag pinned at 100% for six months is stale. Compare that to "this function is too complex," which starts a debate and stalls the PR.
- The diff is verifiable. Deleting an unreachable branch either compiles and passes tests or it does not. Reviewers can approve it in a couple of minutes because the question they are answering is narrow.
- The volume is real. At 1,000 services, flag debt accumulates continuously and nobody is ever assigned to clean it, because each individual flag is a fifteen minute job that never outranks anything.
Uber published essentially this exact approach with Piranha, an open source AST-based tool for deleting stale flag code, and the paper is worth reading before you build anything (ICSE-SEIP 2020). Their deployment numbers are also the best available benchmark for what a healthy loop looks like: cleanup diffs generated for 1,381 flags, 65% landed with no changes at all, over 85% compiled and passed tests, and 75% were processed within a week. If your merge rate is far below that, your rules are the problem, not your developers.
The difference in 2026 is that an agent does not need a hand-written AST refactoring per language. That matters when your estate spans TypeScript, JavaScript, Java and Python, because the tooling cost of supporting a fourth language used to be the reason these projects stopped at two.
Here is where I should give you a triumphant number, and I am not going to, because we did not get one.
We opened more than 100 automated pull requests. About 22% of them merged.
That is the real result. I am publishing it because a 22% merge rate is more useful to you than the number I wish I had. For reference, Uber reported 65% of Piranha diffs landing with no changes at all and 75% processed within a week. We are nowhere near that yet, and the gap is the interesting part of this whole exercise.
The second number is better. Among the services and teams that did merge, change failure rate improved by 7%.
I want to be careful about what that does and does not prove. Those teams selected themselves. A team with the review capacity and the operational discipline to work through a queue of automated deletion pull requests is, on average, already a healthier team than one that ignored them. Some of that 7% is very likely the teams, not the tool. I cannot separate the two with a sample this size, and anyone who tells you they can from a single-org rollout is selling something.
What I will claim is narrower and still worth having: deleting dead paths did not degrade reliability, and directionally it helped. For a change class that ships pure deletions into production across a large estate, "measurably did not make things worse" was the bar I actually cared about clearing. The 7% is upside.

The part nobody puts in the blog post: you just moved the bottleneck
Here is the honest objection, and if you are running a real team it is the first thing you thought of. Review was already your constrained resource. Now a bot is filling the queue.
This is not hypothetical. Telemetry across 22,000 developers found median time in pull request review up 441%, pull request size up 51.3%, bugs per developer up 54%, incidents per pull request up 242.7%, and 31% more pull requests merging with no review at all (Faros analysis of the DORA data). That last number is the one that should worry you. Under enough queue pressure, review does not slow down. It stops happening.
So the loop needs brakes. Two of ours worked, and the two we skipped are the ones I would build first if I started again.
What we did:
- A hard cap on open automated pull requests per team. Two to three at a time. The scanner does not open the next one until something merges or closes. This is the single most important brake, because it means a team that ignores the system is never buried by it, and a team that engages always has a short queue rather than a backlog.
- Batching. Related mechanical fixes across a module ship as one reviewable change, not fourteen.
What we should have done:
- Auto-close on silence. We had none, and with most pull requests never being touched, that means the queue was permanently full of ignored work. The cap of two to three plus no expiry equals a jammed pipeline: those slots never freed up, so the scanner could not offer that team anything else, ever. If I built this again, an untouched pull request would close itself after the two week window and return the item to the candidate pool. No nagging, no stale queue, and critically, no team permanently locked out of the system by its own inbox.
- A stated kill threshold per rule. We never set one in advance, which meant we had no agreed point at which a rule is the problem rather than the team. Setting it after you have seen your numbers is not a threshold, it is a rationalization. Pick the number before you launch.
Merge rate is doing double duty here. It tells you whether the fixes are practical, and it tells you whether review has capacity left. A falling merge rate means stop opening pull requests, not push harder.
What did not work: reading a 22% merge rate honestly
My first instinct was that this is an awareness problem. The loop works, the fixes are fine, the teams just do not know about it yet. More internal comms, a demo at the engineering all-hands, some evangelism, and the number climbs.
I distrusted that instinct, because it is the most comfortable explanation available to the person who built the thing. It puts the fault outside the system. So we looked at what happened to the other 78%, which is the question that actually matters. A pull request that was opened, reviewed, and explicitly closed is a suggestion quality failure. A pull request nobody ever opened is an attention failure. Same number, completely different fix.
Almost all of ours were never touched. Not rejected. Not argued with. Never opened.
Then we noticed the pattern that reframed the whole thing. Adoption was bimodal. Once a team merged a single pull request from the system, they handled the rest of the ones addressed to them too. Teams did not partially engage. They either ignored everything the system sent them or they worked through all of it.
That tells you the barrier is not quality and it is not capacity. It is first contact. I should have expected this. When I was part of a study of AI coding assistant rollout across a 400+ engineer organization, the same shape showed up: the tooling was rarely the constraint, and the distribution of adoption was far more uneven than any aggregate number suggested. The cost a team is refusing to pay is the one-time cost of the first review: figuring out what this bot is, whether the diff can be trusted, whether merging it is their call. Once that cost is paid, the marginal cost of the next one is near zero and the merge follows. Nobody who evaluated a fix decided it was wrong. They decided not to evaluate.
Which means my original instinct was closer to right than I gave it credit for, but the intervention it implies is completely different. Broadcast evangelism does not fix an activation threshold. Landing one pull request per team does. If I ran this again I would not open 100 pull requests across the estate. I would pick ten teams, walk the first pull request through with a human on the thread, and let the loop take it from there. Convert the team once and the system converts itself.
Two other causes worth checking before you assume yours behaves the same way:
- Routing. At 1,000 services, ownership metadata drifts. A pull request assigned to a team that no longer owns the code is not a review problem, it is a stale ownership problem, and it is invisible unless you go looking. Some fraction of "never touched" is always really "never delivered."
- Incentive asymmetry. Merging a deletion pull request carries real downside risk (if it breaks, it is your incident) and close to zero upside (no roadmap credit, no ticket, no visibility). Ignoring it is rational under delivery pressure. That is the incentive working as designed, not a discipline failure, and it is the strongest reason the first contact never happens on its own. This is the kind of thing delivery metrics never surface, which is roughly the argument behind Psychological Productivity Engineering.
The honest summary: the automation worked and the adoption did not, and those are separate problems with separate fixes. Getting a machine to write a correct deletion diff turned out to be the easy half.
Who owns it when it breaks
Decide this before you turn the loop on, because you will be asked during your first incident review.
Our answer: the merger owns the merge. An automated pull request is a proposal, exactly like a proposal from a colleague. The reviewer who approves it accepts the change the same way they would accept anyone else's. The platform team owns the scanner rules and the agent's grounding, and is accountable when a rule produces a pattern of bad suggestions, not when a single merged change misbehaves.
The related decision is test coverage, and this is where the time savings can quietly collapse. If the generated change touches a file below your coverage threshold, do not put the burden on the reviewer to notice. Either require the agent to produce the test in the same pull request, or block generation on those paths entirely and route them to a human. "The reviewer will add the missing coverage" is not a policy, it is a hope.
The signals that actually tell you it is working
Merge rate on automated pull requests, inside a fixed window. High means the suggestions are practical and review has room. Low means one of those two is false, and you need to know which before you do anything else.
Production reliability in the services you touched. DORA metrics give you the frame: deployment frequency, lead time for changes, change failure rate, and time to restore. The point of watching them here is narrow. You are asking whether the specific modules the loop cleaned up show fewer incidents than they did before, not whether your org-wide numbers improved. Ours moved 7% on change failure rate in the merging services, with the selection caveat above attached to it permanently.
Also worth tracking: review time on debt pull requests versus feature pull requests, incidents traced to recently cleaned areas, and whether reviewers are leaving comments that suggest the change reduced future work rather than just passing tests. If you are building the wider measurement picture, Beyond Lines of Code covers how these fit together without turning into a scoreboard.
One caution on measurement generally. In a randomized controlled trial, 16 experienced developers working in repositories they knew well forecast that AI tooling would make them 24% faster, and were measured 19% slower (METR, July 2025). METR has since flagged selection effects in that study and is revising the design, so do not treat 19% as a law of nature. Treat the gap between perceived and measured as the finding, because that gap is the reason you instrument this loop instead of asking the team how it feels. I went deeper on why AI widens that specific gap in How AI Disrupts Productivity Measurements.
The same theme runs through DORA's 2025 report, which concluded that AI functions as an amplifier of an organization's existing strengths and dysfunctions rather than a fix for either (State of AI-assisted Software Development). If your review process is weak, automating pull request generation will make it weaker faster.
Where to start on Monday
Pick one service that has caused a recent incident. Do not pick the worst one in the estate, pick one with an owning team that has review capacity this month.
Run the scanner against it with exactly one rule. If you use feature flags, make it stale flags, for all the reasons above. Cap it at two or three open pull requests, and walk the first one through with a human on the thread rather than letting it arrive cold. That first merge is the thing you are actually buying. Track merge rate and the service's change failure rate for four weeks. If merge rate holds above your threshold and nothing degrades, widen the ruleset before you widen the scope.
Four weeks of data from one service tells you more about whether this fits your organization than any amount of planning does. And it costs you almost nothing if the answer turns out to be no.
Questions I get asked
Which debt items should the scanner surface first? The ones in services already showing elevated error rates or slow deployments. Not the ones with the worst static analysis scores. This is the difference between moving a production signal and doing cosmetic cleanup that closes tickets.
Does this work without a custom setup? Yes, and the first version should not be custom. Off-the-shelf scanners plus a generic agent with a good convention file gets you most of the loop. The grounding investment pays off in review time, which means it pays off only once volume is high enough to notice. Do not build it first.
What if production signals do not improve after dozens of merges? Two likely causes. Either the scanner rules are flagging things that do not correlate with reliability, or the merged changes are not actually reaching production, which happens more than people expect in long-lived branch setups. Check the second one first. It is faster to rule out.
How do I justify this to leadership? Not as a cleanup budget. Frame it as capacity you are recovering and reliability you are protecting, which is the connection I make in Productivity Engineering: 4 Pillars That Drive Business Impact. The change failure rate number is the one that travels; the merge rate is for you, not for them.
Does this replace larger refactors? No. It reduces how often a refactor becomes urgent. You still need the occasional deliberate architectural change, and you should be more willing to fund one when the steady stream of small fixes has already cleared the noise around it.
Further reading: Google's Defining, Measuring, and Managing Technical Debt (IEEE Software, 2023) on why measurement is the hard part, and ACE: Automated Technical Debt Remediation with Validated LLM Refactorings for the research direction behind agent-generated fixes.
More on this topic: everything filed under Tech-Debt and AI, or start with What is Productivity Engineering if this is your first post here.
Comments ()