Skip to content

How to Find and Remove Dead Code Safely with AI (Copy-Paste Prompt)

A dead code audit prompt for Cursor, Claude Code, and Codex that finds unused and do-nothing functions, proves they are dead with references and git history, and reports before it deletes anything.

Jump to the prompt

If you have been vibe coding (or even just going fast) with Cursor, Claude Code, Codex, or another AI coding agent for any length of time, there is a decent chance your codebase has started collecting things.

You try something, it doesn't work, you change direction, and the agent tries something else. Eventually you get where you wanted to go, but there can be a lot left behind along the way: unused functions, helpers from an earlier implementation, or functions that still get called but don't really do anything anymore.

This is obviously not just an AI problem. Every codebase accumulates dead code. AI just lets us produce code so quickly that we can also produce the leftovers quickly.

The tempting thing to do is point the agent at the repo and say "find and remove all the dead code." (don't do that!) I'm not particularly comfortable doing that because finding code that looks dead is relatively easy. Proving it is actually dead before deleting it is the important part.

Why dead code gets tricky with AI-generated code

When I'm moving quickly with an AI coding agent, the implementation doesn't always travel in a straight line. I might start with one approach, find out it isn't working, and have the agent change direction or rewrite part of it. Sometimes a function gets replaced, sometimes a helper is created for something that eventually gets handled somewhere else, and sometimes we just abandon an approach altogether once we find a better one.

At some point the feature works and we move on, which is usually the point. But come back six weeks later and there may be three functions that appear to participate in the feature when only one actually does.

That's annoying for us, but I think there's another problem here that's becoming more important: AI agents have to read this stuff too.

When an agent comes into the repository later, it has to figure out which implementation is the right one. An unused helper can look every bit as legitimate as the function that actually runs, and old code can still show up in searches, get pulled into context, and influence whatever the agent is working on next.

So now code an AI left behind can become context for what another AI builds later. I'd rather get that stuff out of there.

But "unused" doesn't necessarily mean dead code

This is the part I don't want the AI guessing about.

Static analysis can give you a pretty good starting point, and tools like Knip, Vulture, staticcheck, compiler warnings, and language-specific analyzers can find things that appear unused. But appearing unused and actually being safe to delete aren't necessarily the same thing.

A function might not have an obvious direct call because it's being used through a framework convention, decorator, event listener, plugin registry, configuration file, CLI entry point, scheduled job, or some dynamic lookup. It could also be a public export being consumed somewhere the analyzer can't see.

So instead of giving the agent a job that amounts to find unused code → delete it, I want it to find candidates → investigate them → show me the evidence → then decide what gets changed.

That puts a little friction between detection and deletion, which is exactly where I want it.

What I want the AI agent to do with the dead code review instead

I put together a dead-code audit prompt that separates finding things from deleting them. The agent starts by figuring out what languages and packages are in the repository and then uses whatever dead-code or lint tooling the project already has. If there isn't anything, it picks an appropriate tool for the language.

Whatever that tool finds is just the starting point. Every result is treated as a candidate, not proof, and from there the agent has to investigate it. I want it checking references, looking for dynamic usage and framework conventions, checking config, entry points, tests, public exports, and even git history before deciding what the function actually is.

Then I want it to classify what it found.

Remove means there are no live references and no apparent reason for the function to still exist. Hook up is something different: maybe the function isn't dead at all, but somebody built it and never connected it, or it belongs to something unfinished. Rework means there is a real purpose there, but the implementation isn't doing what it should.

There are also functions that are only referenced by tests or other dead code, false positives that are actually being called dynamically, and cases where there simply isn't enough evidence to know. Those last ones get marked Uncertain, because I'd much rather have the agent tell me "I don't know" than confidently delete something it doesn't understand.

The important part: it can't change anything yet

The first three phases of the prompt are read-only. The agent establishes a test baseline, runs the analysis, investigates what it finds, and gives me a report before it's allowed to touch anything.

I don't want opportunistic refactoring or dependency upgrades sneaking into this, and I definitely don't want a dead-code audit somehow turning into an architectural rewrite. I want something I can actually review:

Function Evidence checked Classification Confidence Recommendation Risk
buildLegacyPayload() repo search, imports, tests, git history Remove High Delete Low
syncAccount() calls, cron config, git history Hook up Medium Review intended scheduled job Medium
registerPlugin() static refs, registry config False positive High Keep High

Now I can look at what it found and, more importantly, why it thinks the function is dead. Only after I approve that report can it start making changes.

The dead code audit super-prompt

Paste this into your coding agent from the root of the repository. Change [repo/directories] to wherever you want it looking, and fill in anything you specifically want excluded.

# Dead Code Audit

Goal: Find functions in [repo/directories] that are unused or do nothing, verify they're truly dead, and recommend remove, hook up, or rework for each. No behavior changes.

Out of scope: [generated code, vendored code, migrations, etc.]

## Ground rules

- Work on a separate branch and run the full test suite first for a baseline.
- Phases 1-3 are read-only. Change nothing until Phase 4 is approved.
- Don't modify project dependencies or config. Run tools via npx, pipx run, or a global install.
- If you're unsure whether something is dead, mark it "Uncertain" rather than guessing.

## Phase 1: Discovery

1. Detect the languages in use from manifest files (package.json, pyproject.toml, go.mod, pom.xml, etc.). Treat monorepo packages separately.
2. For each language, use the project's existing dead code or lint tooling. If none, choose the standard one (e.g., vulture, knip, staticcheck, Roslyn analyzers, compiler warnings). State your choices before running them.
3. Treat tool output as candidates only. Then review file by file to catch what tools miss, including functions that run but do nothing.
4. Verify each candidate has no references via: direct calls, dynamic or string-based lookups, decorators and framework conventions, event listeners and plugin registries, config, CLI entry points, scheduled jobs, public exports, or tests.
5. Check git history for why it exists and whether it was ever used.

## Phase 2: Classification

Label each function:

- Remove: no live references, no planned purpose
- Hook up: half-finished or tied to a planned feature/ticket
- Rework: has a purpose but is broken
- Test-only / dead-chain: referenced only by tests or other dead code
- False positive: used dynamically or by convention
- Uncertain: needs human input

## Phase 3: Report

Deliver a table with these columns:

| Function | File:Line | Evidence checked | Classification | Confidence (H/M/L) | Recommendation | Risk |
|---|---|---|---|---|---|---|

Stop here and wait for review.

## Phase 4: Changes (after approval only)

- Make small, separate PRs grouped logically.
- Run the full test suite before and after. Results must match or beat baseline.
- Don't touch public APIs or exports without explicit sign-off.

The line I care about most in there is "Treat tool output as candidates only." If Knip says something is unused, great, that's evidence, but it isn't permission to delete it, yet. The agent still needs to figure out whether the function is actually dead.

Why check git history?

This is one of the more useful parts of the prompt because a function having zero references doesn't necessarily tell you why it's there. Maybe git log shows that it was added three days ago as part of a feature that hasn't been wired up yet. That's not really dead code, that's unfinished code, and I'd want to know that before it disappears.

On the other hand, maybe the function belonged to an implementation that was replaced eight months ago, nothing has referenced it since, and the rest of that feature has moved on. That's a much stronger case for removing it.

The current code can tell the agent what exists, but git history can sometimes tell it why it exists, and that's useful context before we start deleting things.

What about dead code that actually runs?

This one is a little different, and it's something static analysis can easily miss. A function can be imported, called, and pass type checking while still doing basically nothing useful.

Maybe the body got gutted during a refactor, it returns a placeholder, an error path short-circuits the useful behavior, or the agent created the function with every intention of finishing it before we changed direction. A dead-code tool may look at that function and say everything is fine because, technically, it has references.

That's why I also want the agent reading the functions themselves instead of just handing me the output from Knip or Vulture. If something gets called but doesn't actually do anything useful, I want to know about that too.

When I run this

For me, this is mostly a cleanup pass after I've been moving quickly. Maybe I've spent a couple weeks building a feature, bounced between approaches a few times, let an agent refactor a chunk of the codebase, or told it "no, that's not what I meant, try this instead" more times than I'd like to admit.

That's usually when I want to run this. Not because vibe coding inherently creates bad software, but because when it's this easy to try another implementation, it's also really easy to leave pieces of the previous one sitting around.

So clean them up. Just don't ask the same agent to blindly delete everything it thinks you aren't using. Make it prove it first.

Frequently asked questions

What is considered dead code?

Dead code is code that no longer serves a useful purpose in the application. That can include a function that is never called, code that can no longer be reached, or a function that technically gets called but no longer does anything useful. The tricky part is that unused code and dead code aren't always the same thing. A function with no obvious references might still be loaded dynamically, called by a framework convention, registered through configuration, or exposed as part of a public API. Tools such as Knip build a picture of what's reachable in a project, but even Knip's documentation points out that missing entry points or dynamic imports can make working code appear unused. That's why I treat "unused" as something to investigate rather than something to immediately delete.

How do I find dead code with AI?

Have the AI combine language-specific static analysis with repository-wide reference checks, but don't treat the static-analysis results as proof. Have the agent check dynamic usage, framework conventions, configuration, registries, CLI entry points, scheduled jobs, tests, public exports, and git history before removing anything. For JavaScript and TypeScript projects, Knip's documentation on how its analysis works is worth reading because it explains how entry points and reachability affect what gets reported as unused.

Can Cursor or Claude Code remove unused code automatically?

Yes, but I wouldn't give it permission to find and delete everything in one pass. Have the agent produce the dead-code report first, review what it found and the evidence behind it, then let it make the changes. Some underlying tools can also make changes automatically. For example, Knip supports auto-fixing unused code and dependencies, but its own documentation recommends reviewing the resulting changes. The prompt above deliberately adds another review step before anything is changed.

What tools can find dead code?

It depends on the language and repository. Knip is one option for JavaScript and TypeScript, Python projects can use Vulture, Go projects can use Staticcheck, and .NET projects can use Roslyn analyzers. Your compiler or linter may already be catching some of this too, so I would start with whatever the repository already uses before adding something else.

Is unused code always safe to delete?

No. Something that appears unused may still be invoked through reflection, dynamic imports, decorators, framework conventions, configuration, event systems, plugin registries, scheduled jobs, or external consumers. Knip calls one version of this a "reachability gap": code may be in use even though the analyzer can't reach it from the entry points it knows about. Their cleanup guide has a good explanation of this problem. "No references found" is good evidence. It isn't proof.

Why remove dead code if it isn't hurting anything?

Because you still have to read it, your team still has to read it, search still finds it, and AI agents can still pull it into context. Everyone has to spend some amount of time figuring out whether it matters. And if an AI agent can't tell which function is real and which one is leftover from three implementations ago, you're giving it bad context for whatever it builds next.

Comment on this post →

This site does not use cookies. Anonymous, privacy-friendly traffic data is collected via Vercel Analytics. .