Measure dead code growth before AI repos slow your team
Learn how to measure dead code growth with file age, call counts, and owner gaps so you can remove low-value code before AI-heavy repos get messy.

What dead code does to AI-heavy repos
AI tools make code cheap. Judgment gets expensive.
In an AI-heavy repo, ten files can appear before anyone fully reviews one. Experiments, helper modules, copied patterns, and half-finished retries pile up fast. The problem is not just extra code. It is extra doubt.
Old files keep showing up in search results, autocomplete, and pull requests long after the product stopped using them. A developer finds a cleanly named module and assumes it still matters. Then they copy its pattern, ask about it in review, or avoid changing it because it looks risky.
That drag is easy to miss because it arrives in small pieces. Search gets noisy. A query returns the current implementation, two abandoned versions, and a migration script from months ago. Reviews slow down for the same reason. A diff touches a file, nobody knows who owns it, and the team stops to guess whether some hidden dependency still exists.
Five minutes here, ten there, a few cautious chat messages, and one engineer decides to leave the suspicious file alone. Across a busy team, that turns into hours every week.
The bigger cost is trust. A repo should answer one simple question: "How does this product work today?" When unused code lingers, that answer gets blurry. People stop trusting the codebase as the source of truth and fall back to memory, old tickets, or the one teammate who remembers why a folder exists.
Dead code is not just clutter. It makes decisions slower and less reliable. In AI-heavy repos, where code appears quickly and styles vary, that cost shows up early.
The three signals worth tracking
Teams usually spot dead code late. AI tools make that worse because they write fast, copy patterns, and leave behind files that still look believable after the product has moved on.
The safest way to track dead code growth is to watch three signals together: file age, real usage, and ownership. Any one of them can mislead you on its own.
File age
A file that nobody touched for eight or twelve months deserves a second look, especially if the rest of that part of the product changed several times. Age does not prove a file is dead. Some code stays quiet because it is stable. Still, old files often collect abandoned experiments, stale helpers, and half-finished integrations the team forgot to remove.
Call counts and owner gaps
Usage tells you whether running code still reaches a file. Check imports, route hits, background jobs, scheduled tasks, CLI commands, and test coverage. A file with near-zero usage in production paths and tests is a stronger cleanup candidate than an old file that still runs every day.
Ownership answers a different question: who feels responsible for this code now? When nobody owns a file, people avoid changing it. That is how clutter survives for months. You can usually spot this in files with no clear recent author, no steady reviewer, or comments that hint that nobody is sure who should touch it.
These signals get more useful when you combine them. Old, unused code with no owner is often safe to remove after review. Old code that still runs but has no owner needs a clear handoff before anyone deletes anything. New code with low usage often points to an abandoned experiment, not long-term dead code. Old code with low usage and an active owner usually needs a quick conversation, not a cleanup PR.
Picture an internal AI feature that shipped six months ago and then got replaced. The old prompt builders still sit in the repo. Logs show no calls. The original engineer moved to another area. That file is a strong removal candidate.
Build a simple score for cleanup
A cleanup score helps you stop arguing from gut feel. Give every file a few points for age, usage, and ownership. The score does not decide what to delete. It tells you which files deserve a closer look first.
Start with age. If a file has not changed in 9 to 12 months, add points. If it stayed untouched through several releases, add more. Old code is not always dead, but old untouched code often survives because nobody wants to spend time proving it still matters.
Then lower the score when the code still does real work. If tests hit it often, reduce the risk. If production logs, traces, or call counts show steady use, reduce it again. A file can look forgotten in Git and still sit on a hot path every day.
Ownership gaps should push the score up fast. Add points when the last person who touched the file left, when several people edited it in small bursts and nobody owns it now, or when nobody on the team can explain why it exists. In AI-heavy repos, this happens a lot with old prompt runners, one-off evaluation scripts, and generated helpers.
A simple model is enough:
risk score = age points + owner gap points - test usage points - runtime usage points
You do not need fancy math. A plain scale works well:
- Age: 0 to 4 points
- Owner gap: 0 to 3 points
- Test usage: subtract 0 to 2 points
- Runtime usage: subtract 0 to 4 points
A stale file with no owner and no runtime calls might land at 6 or 7. A two-year-old module that still gets hit in tests and production might drop to 1 or 0, which tells you to leave it alone.
Sort files by score and review the top group first. For most teams, the top 20 files or top 5 percent of the repo is enough. That keeps the review small enough to finish.
One product team tried this on old model adapters and background workers. The worst-scoring files were not risky to remove. They were leftovers that kept slowing reviews and making AI-generated edits less reliable.
Review candidates step by step
Do not start with the whole repo. Start with the top 20 files from your cleanup score. That gives you a small set you can inspect with care. In AI-heavy codebases, old wrappers, duplicate helpers, half-finished prompt chains, and unused API routes usually rise to the top quickly.
Open each file and ask a direct question: who still calls this? Search imports, route bindings, job schedulers, CLI commands, and feature-flag branches. If nothing points to the file directly, check for registries, reflection, or config entries. AI-generated code often hides usage in places people forget to inspect.
Then check three sources of recent life: logs, tests, and commit history. Logs tell you whether production still touches the code. Tests show whether any behavior still depends on it. Recent changes show whether a developer still cares about it, even if call counts are low.
A file with zero calls but a fresh bug fix is not dead. A file with old commits, no log activity, and no test coverage is a much stronger candidate.
A simple label helps keep the review honest:
- Keep if the file still supports a live path.
- Merge if it duplicates logic that already lives elsewhere.
- Archive if you want it for reference but not in active paths.
- Remove if nothing calls it and nobody owns it.
Owner gaps matter more than teams admit. If nobody can explain why a file exists, treat that as a warning sign. At the same time, do not delete code just because the original author left. Check production behavior first.
Delete in small batches. Five files is usually enough for one pass. Ship the change, watch error tracking and logs for a day or two, then continue.
That pace feels slow, but it beats one large cleanup that breaks a forgotten workflow. Teams running lean AI-driven engineering setups tend to feel this benefit sooner because small piles of extra code turn into confusion very quickly.
A realistic example from a growing product team
A small SaaS startup added AI features fast. First came a prompt wrapper for support replies. Then the team added a backup wrapper for a second model. A month later, another engineer wrote a retry wrapper inside a background worker so failed requests would not block the app.
Six months later, the repo had three files that looked different but did nearly the same job. Each one built prompts, called a model, logged token use, and returned text. One lived in the web app, one in the worker, and one in an old experiments folder that still shipped with production code.
The hard part was not technical. Nobody could say which wrapper production still used.
So the team stopped guessing. They checked three signals for each file: when it last changed, how often other files called it, and whether one person still owned it. That gave them a practical way to measure dead code growth instead of arguing from memory.
Their notes were simple:
prompt_wrapper.ts: changed last week, 41 call sites, clear ownerfallback_prompt.ts: changed 4 months ago, 3 call sites, no ownersafe_ai_client.ts: changed 6 months ago, 1 call site, former owner left
The oldest file was not automatically the first one to cut. The quiet files were. One wrapper still had many call sites, so the team left it alone. The other two barely connected to the rest of the app, and the engineers traced those last callers to retry paths that no longer handled live traffic.
They removed the lowest-scoring file first and watched logs, alerts, and support tickets for a few days. Nothing changed. Then they removed the second quiet wrapper and kept a small rollback patch ready in case they had missed a hidden dependency.
The win was bigger than a few hundred deleted lines. New engineers stopped opening three similar files and asking which one mattered. Reviews got faster because people no longer compared duplicate logic. That is usually the real gain in dead-code cleanup for AI repos: less noise, fewer wrong turns, and better judgment when the next helper, agent, or prompt layer shows up.
Mistakes that lead to bad deletions
Teams usually delete the wrong code for one reason: they treat weak signals as proof. A file with low call counts might still protect a billing retry, a compliance export, or a recovery path that only runs when something breaks. Rare use does not mean no use.
This gets worse in AI-heavy repos. Agent-generated code often creates wrappers, adapters, and helper files that look quiet in normal app traffic. If you only count direct calls, you can miss imports created during build steps, background jobs, or generated clients.
Another common mistake is trusting product traffic and ignoring everything else. Cron jobs, admin tools, migration scripts, and support-only commands can sit dormant for weeks and then matter a lot on one stressful day. Delete one of those paths and you may not notice until a refund batch fails or an on-call fix stops working.
Shared helpers can fool you too. A helper may look unused in the main codebase while generated code still pulls it in. Search tools often miss that relationship when imports come from templates or code generation. Before you remove a shared helper, check generator output, build artifacts, and any internal SDKs that depend on it.
Small batches beat cleanup sprees. When a team deletes fifty files at once, reviews get shallow and blame history gets messy. Later, nobody knows whether one file was truly dead or simply swept up in a purge. That makes rollback slower and future cleanup harder.
A safer review usually covers four places: scheduled jobs, admin panels, internal scripts, and fallback paths. If the code touches any of them, slow down. Also search generated code, templates, and build output for hidden imports before you remove anything.
One product team learned this the hard way when they removed a "low-use" export module before quarter-end. Daily traffic never touched it. Finance used it once a month. The deletion looked clean, tests passed, and then the reporting run broke right when people needed it.
Low activity is a clue, not a verdict. Good cleanup is less about speed and more about removing code that nobody depends on, including the odd paths people forget until they need them.
Quick checks before you remove anything
A stale file looks harmless until some hidden job still uses it. AI-heavy repos make this worse because generated code, old experiments, and helper scripts pile up faster than people review them. Before you delete anything, try to prove nobody needs it.
Start with usage outside the main app. Search cron jobs, CI steps, deploy scripts, data imports, support utilities, and admin commands. A file may have zero calls in product code and still run every night at 2 a.m. That kind of surprise costs more than the cleanup saved.
Then ask for a human owner. One person should be able to say, in plain language, why the file exists and what breaks without it. If nobody knows, the file moves up your cleanup list. If someone says, "we might need it," ask for a real case and roughly when it last ran.
A short check works well:
- Hide or rename the file on a branch and run the test suite.
- Run the jobs that tests often miss, such as migrations, exports, and support scripts.
- Watch logs and alerts for a full cycle, not just one local run.
- Prepare a rollback that restores the old state in one commit or one revert.
Tests alone do not settle it. Many teams have weak coverage around setup scripts, internal tools, and support flows. In fast-moving AI teams, this gap gets wider because people generate helpers quickly and rarely write deep tests for them.
That is why rollback matters so much. Cleanup gets easier when the team knows it can undo a bad removal in minutes. A clean revert lowers the stress and leads to better decisions.
How to stop the clutter from coming back
Messy repos rarely happen because one person writes bad code. They grow because small temporary choices never get cleaned up. In AI repos, that happens even faster. Teams try a prompt chain, keep an old wrapper, copy a helper for one model, then forget all three a week later.
A few simple rules stop most of that growth. Give every experiment an expiry date. Add an owner tag when the team creates a new folder. In review, ask one duplication question: "Do we already have a wrapper or helper that does this?" And track cleanup debt next to product work instead of hiding it in a backlog nobody opens.
Expiry dates matter more than people think. AI work creates lots of short-lived code: evaluation scripts, one-off agents, temporary RAG loaders, fallback prompts, and model-specific glue. If nobody sets a review date, that code starts to look permanent. A simple rule works well: review experiments after 14 or 30 days. Keep them, merge them properly, or delete them.
Owner tags solve a different problem. Old folders become risky when nobody knows who can say, "Yes, this still matters" or "No, we replaced it in March." You do not need a heavy process. A short note in folder metadata, repo docs, or team docs is enough if people keep it current.
Code review is the best place to catch duplicate wrappers. AI tools often generate near copies of the same client, parser, or retry logic. Reviewers should treat those copies as maintenance cost, not harmless shortcuts. One extra wrapper does not seem like much. Fifteen wrappers later, nobody trusts what is safe to change.
Cleanup debt also needs a real slot in planning. If the team only tracks features, clutter always loses. Add a small cleanup task beside product work each sprint. That habit does more than a large quarterly purge because it stops the pile from forming in the first place.
What to do next
Start with one folder that already slows reviews down, such as old prompt chains, helper scripts, or a half-replaced service layer. A small test is easier to judge, and it gives your team a safer way to track dead code before touching anything larger.
Run the same score every month. One report tells you very little. Two or three reports show the pattern. If file age keeps rising, call counts stay low, and nobody claims ownership, that folder is drifting toward clutter.
Keep the score simple. File age, call counts, and owner gaps are enough for a first pass. You do not need a perfect model. You need a repeatable one that helps people agree on what deserves a closer look.
It also helps to keep the outcomes simple. Keep files that still support a live path or current workflow. Merge files that split attention while doing the same job. Archive code that might still help as reference but should leave the active tree. Remove code that nobody owns, nothing calls, and no recent work depends on.
Those labels cut down debate. They stop cleanup work from turning into opinion fights. If a file lands in a gray area, mark it for a quick review instead of forcing a deletion.
This approach works especially well for AI teams because experiments create extra code faster than most people expect. A monthly cleanup pass keeps that growth visible and makes ownership gaps obvious before they turn into bigger problems.
If your team needs a second pair of eyes, Oleg Sotnikov at oleg.is helps startups and smaller companies review architecture, ownership, and safe cleanup plans. Sometimes an outside CTO view is the fastest way to separate real risk from repo noise.
Frequently Asked Questions
What counts as dead code in an AI repo?
Dead code is code that no live path, job, script, or team member still needs. In an AI repo, that often means old prompt wrappers, copied helpers, half-finished experiments, or replaced model clients that still look valid but do no useful work.
Why does dead code hurt more in AI-heavy repos?
Because AI tools create code faster than people review it. Extra files then pollute search, confuse reviews, and make engineers hesitate when they cannot tell which version still matters.
Which signals should I check first?
Start with file age, real usage, and ownership. A file gets risky when it has sat untouched for months, logs and tests barely touch it, and nobody can explain why it still exists.
How should I score files for cleanup?
Use a simple score, not a fancy one. Add points for age and owner gaps, then subtract points for test usage and runtime usage so the highest scores show where to inspect first.
Does a high cleanup score mean I should delete the file right away?
No single number proves a file is safe to cut. Treat a high score as a review flag, then confirm imports, jobs, logs, tests, and any hidden config before you delete anything.
How many files should I review in one cleanup pass?
Keep the batch small. Five files or the top 20 candidates usually gives enough progress without turning the cleanup into a risky sweep.
How do I verify that a quiet file still matters?
Check outside the main app first. Search cron jobs, admin tools, support scripts, migrations, generator output, and build steps, then hide the file on a branch and see what breaks in tests and logs.
What kind of code gets deleted by mistake most often?
Teams often remove rare paths that only run during failures or monthly work, such as billing retries, exports, and recovery scripts. Shared helpers also trick people when generated code or internal SDKs still pull them in.
When should I archive code instead of deleting it?
Delete when nothing calls the file, no one owns it, and recent work does not depend on it. Archive when you want the code for reference but do not want it mixed into the active tree.
How do we stop dead code from piling up again?
Give experiments an expiry date, assign an owner when you create new folders, and ask in review whether the repo already has the same helper or wrapper. A small cleanup task each sprint keeps the pile from growing again.