Agents add. They don't subtract. That's the bug.
Who is supposed to delete the dead code your agent leaves behind? Some teams rely on the reviewer. Some trust the agent. Some never look. Here is what we landed on after running aislop against 25 real repos, and why dead code removal was the single biggest score jump we saw.
Who is supposed to delete the dead code the agent leaves behind? Ask around, nobody has a clean answer. Some teams trust the reviewer to catch it. Some trust the agent to refactor cleanly. Some never look at the diff past the feature bullet. Here is what we landed on after 25 projects. Dead code removal was the single biggest contributor to score jumps we saw, and nobody was owning the deletion.
chrome-extension went 9 to 68 on one pass, 88 on the second. A 79 point swing. dailyapp-backend went 27 to 87 to 94. buildwithkenny went 47 to 58 to 100. None of those moves were clever transformations. They were the linter doing the deletion the agent should have done at refactor time. Dead code, unused functions, useless variables. Your agents are leaving them behind.
The pattern is the same in every case. The agent refactored a function. Rewrote the body, renamed it, updated the call sites it could see. Shipped. Three commits later there is a top of file import foo from './old-helper' that nothing uses. A module exporting four functions, two of which are never imported. A try wrapping a call that no longer throws. A legacyMode flag that once gated something. None of that showed up in the diff. The agent refactored by addition, not by replacement, and the orphans stayed exactly where they were.
The four classes of dead
Four rules cover it. Each one catches a different failure mode.
knip/exports
Symbols exported from a module but never imported anywhere in the project. Usually the agent refactored the consumer and left the producer sitting there.
knip/files
Whole files nothing references. Typically a v1 of a helper that got rewritten as helper-v2.ts and never garbage collected. Or a route file the agent thought it was creating fresh when it was actually duplicating an existing one.
ai-slop/unused-import
import x from '...' with no usage in the file. The single most common finding per file across the 25 project run.
ai-slop/dead-patterns
Unreachable code after return. Constant true conditions like if (true). try blocks whose body provably cannot throw. Empty functions with a TODO that has been there for a year.
Why we don't trust upstream --fix
Both oxlint --fix and knip --fix will happily remove dead code. Neither is safe to run unattended. We have four cases from the 0.5 validation run that prove it.
Dangling colon (zingo-web)
Before:
After oxlint --fix stripped the unused alias:
Dangling colon. The file no longer parses.
Rest-element alias (snappymenu)
Before:
After:
Rest elements do not take aliases. The autofixer did not know that.
Typed shorthand rename (buildwithkenny)
After:
Renaming a typed shorthand binding breaks the contract. The fixer treated it like a free identifier.
TDZ from exhaustive-deps autofix (joiner-landing-page)
After autofix added loadScript to the deps:
The dep got added before the variable was declared in scope. Build broke.
Four projects. Four different ways an upstream --fix shipped a TypeScript parse error into committed code. Detection in those tools is excellent. Mutation in those tools is not. The two have to be separated.
Detection stays external. Fix is in-house.
aislop uses oxlint and knip for what they do well. Finding things. Neither is allowed to write to user files. The actual removal runs through an AST engine built on the TypeScript compiler API. Every mutation follows the same contract:
- 1. Classify the binding shape: shorthand property, aliased property, rest element, array binding, positional parameter, catch parameter.
- 2. Apply the rewrite for that shape. Never a regex.
- 3. Parse the resulting file. If it does not parse, revert.
- 4. Write.
Own the destructive fix. That is the principle. Detection is fine to outsource because a false positive in detection is just a missed fix. Mutation is not fine to outsource because a false positive in mutation is a broken file in your commit history. The asymmetry is the whole reason this engine exists.
Parse check before write is the implementation. Every auto fix proves its output parses before it persists. Slower than a regex by a factor of ten. Also the only reason aislop fix -f can run unattended in CI without anybody losing sleep over it.
What the dead code is costing you
Easy to say "it is just a few unused imports." The cost is not size. The cost is signal. Every orphan is a future bug report waiting. Someone imports the dead function thinking it is live, and it silently misbehaves. Someone greps for a name and gets three hits, only one of which actually runs. The next agent passes through and the dead code becomes part of its training context for the next answer. Now you have fresh code shaped by a ghost.
Dead code also inflates the context window of every agent working in the file. A 400 line file that should be 240 lines is 40% more tokens to reason about. 40% more chances to trip on a stale reference. 40% more friction before every small change. The tax compounds with every commit, and the agent never feels the friction it created.
Pick the tool
Either your tooling removes dead code, or your codebase has dead code. There is no third option. Agents do not clean up after themselves. Reviewers skim the additions and not the leftovers. Every week of shipping adds more orphans than the week before. aislop scan finds them. aislop fix removes them through the AST. aislop fix --claude hands the rest to an agent. The chrome-extension's +79 swing was not magic. It was the gap between "an agent has been writing here" and "a tool has been reading here."
Try it on your repo
Run it. See what the agent left behind. Star the AI Slop CLI on GitHub if you want the next release in your feed.