Issue 00 · Autumn 2026日本語 · RSS
AgentKoji

Deleting Thousands of Lines in a Refactor

When a Claude Code refactoring deletes thousands of lines, remove callers first. How to map dependency direction and how to slice commits, reproduced locally.

Deleting an old subsystem is scarier than adding one. Ask an agent to “remove this old module” and the deleting is quick; the trouble is that the order breaks intermediate states and everything lands in one giant commit. Here is an order for deletions of thousands of lines that keeps each step working. We reproduced it in a local dummy repo (Python 3.13.5, git 2.47.3, 2026-10-16). The reproduction used a few lines of dummy code; we did not measure it at the scale of thousands of lines.

Map the dependency direction first

In the dummy repo, an old formatter (legacy_fmt) uses an older helper (legacy_core), and two modules call the formatter; a replacement already exists. We extracted imports and ordered them with Python’s graphlib:

== dependency graph (from imports) and deletion order
users of legacy: ['invoice', 'report']
order (consumer→provider): replace callers → legacy_fmt → legacy_core

Delete consumers before providers. Let the agent build the map, but regenerate it with grep or a script and compare.

Deleting backwards breaks the middle

Agents tend to go for the oldest, lowest part first. Deleting the helper first:

FAILED (errors=1)

At scale, in our experience (not measured here), errors tend to scatter and the agent starts “fixing” them one by one, sometimes by copying the deleted code somewhere else.

One step, one commit

28c1d90 step3
 1 file changed, 2 deletions(-)
9648f75 step2
 1 file changed, 3 deletions(-)
9bd2477 step1
 2 files changed, 2 insertions(+), 2 deletions(-)

Tests passed at every step. Replacement and deletion are separate commits, so any one can be reverted and reviewed on its own. The docs also advise refactoring in small, testable increments (https://code.claude.com/docs/en/common-workflows, checked 2026-10-16).

Careful with parallel splits

/batch researches the codebase, splits work into 5 to 30 independent units, and after approval runs a background subagent per unit in an isolated worktree, each running tests (https://code.claude.com/docs/en/commands, checked 2026-10-16). Deletion steps are ordered, not independent: deleting a provider in one unit while callers are replaced in another is green in each worktree and broken once merged. Parallelize only within one stage.

Prompt

Delete the following modules. Do not delete immediately.
- First, list callers of the targets and dependencies among the targets.
- Delete from consumers to providers.
- Run tests after each step and commit each step; keep replacements and deletions in separate commits.
- If tests fail, do not fix by copying code elsewhere. Stop and report.

Count from the starting commit

$ git diff --shortstat HEAD~3 HEAD
 4 files changed, 2 insertions(+), 7 deletions(-)
$ git diff --name-status HEAD~3 HEAD
M	app/invoice.py
D	app/legacy_core.py
D	app/legacy_fmt.py
M	app/report.py

D means deleted, M modified. New A files in a deletion refactor suggest the removed code was copied somewhere.

After deleting

Passing tests don’t prove the deletion was right; they may never have exercised what you removed. Grep the whole codebase for removed names, and don’t decide “unused” from import greps alone: see Green Tests Prove Nothing. Don’t trust “deletion complete” either; check line and commit counts with git log --shortstat. Mechanically matching reports to facts is covered in Don’t Trust an Agent’s “Done”.

Summary