I priced my own dead code and got a $775 bill
Every developer has a graveyard. Mine was 35 files and 2,317 lines, and I couldn't have told you what half of it did. So I built GhostCode, a dead-code analyzer that reads your AST instead of guessing from text, and the first repo I ran it on was my own.
Here is what I built, how it works, and what the numbers said.
Dead code is a P&L line item, not a vibe
Dead code doesn't just sit there. Someone reads it during onboarding, debugs around it, and keeps it passing CI. That's real time, so I wanted a number: how much does this code cost me per year?
The crude version I started with: every dead line costs $0.50, scaled by complexity. Crude on purpose. A precise-but-wrong number is worse than a crude-but-directional one.
How it works: four layers
1. Ingestion. tree-sitter parses Python and JS/TS into real ASTs. No regex guessing, no string-matching for "import". If the parser can't see the symbol, it doesn't count.
2. Graph. Everything becomes a NetworkX graph: files, functions, classes, imports. A "ghost node" is any node with zero incoming edges. Nothing calls it, nothing imports it, nothing references it. That's dead code by construction, not by opinion.
3. Scorers. Three of them:
- Dead-code scorer: how confident are we this is actually dead (deletion confidence)?
- Debt scorer: how tangled is the code that IS alive? Circular imports, complexity hotspots.
- Cost scorer: dead LOC x $0.50, scaled by cyclomatic complexity. A simple dead utility is cheap. Dead code wrapped in nested conditionals that future-me has to read is expensive.
4. Dashboard + auto-PR. A Next.js dashboard over a FastAPI API (Docker, deployed on Render), and PyGithub opens the deletion PRs for you.
Dogfooding: my own repo first
35 files, 2,317 lines. The scan found 10 ghost files and 21 ghost symbols. Three circular import chains I didn't know about. Total: $775.49 of annual maintenance burn.
Some of it stung. A helper module I was "keeping in case I need it later" turned out to have zero incoming edges from day one. It was never needed. Deleting it took one PR and the tests stayed green, 91 out of 91.
What I'd tell you before you build this yourself
Start with tree-sitter, not regex. Regex-based "unused code" tools lie to you the moment a dynamic import shows up.
Zero incoming edges is the right definition. It's falsifiable: show me one caller, or it's dead.
Price it crudely early. The $0.50 number is wrong, but it turned "should I clean this up" into "this file costs $120 a year to keep." That's a decision, not a feeling.
My repo now has 10 fewer files. The analyzer runs in CI now, so the graveyard doesn't grow back.
Top comments (0)