teachyou.ai academy
← All posts
Claude Coderefactoringcodebase migrationAI coding agentsdeveloper productivity

Large-Scale Refactoring with Claude Code

Pramod Dutta · Jul 5, 2026 · 14 min read

Claude code refactoring is the practice of using Claude Code, the CLI-based coding agent, to plan and carry out structural changes across a codebase: renaming APIs, splitting monoliths, migrating frameworks, deleting dead code, or changing a data model that touches hundreds of files. Done well, it turns a multi-week refactor into a multi-day one without sacrificing correctness. Done badly, it produces a huge diff that compiles but silently breaks behavior nobody tested. This article is a working playbook: how to scope the change, how to brief the agent so it does not go rogue, how to run it in parallel across a large tree, and how to verify the result before you trust it.

The short answer to "how do I use Claude Code for a big refactor": break the refactor into small, independently verifiable steps, give the agent a plan and file-level scope before it edits anything, run changes in a git worktree so your main branch stays clean, and gate every step behind tests or a build, not eyeballing the diff. The rest of this piece works through each of those in detail with real commands.

Why refactoring is a different job than feature work

Feature work has a target state you are building toward from nothing. Refactoring has an existing, working system, and the entire point is that behavior must not change while the internals do. That inverts the risk profile. A bug in new code is annoying. A bug introduced by a refactor is worse, because it hides inside code that "looks the same" and nobody re-reviews it with fresh eyes.

Claude Code refactoring work should treat the existing test suite (or the absence of one) as the actual spec. If there is no test coverage for the area you are touching, writing characterization tests before the refactor is not optional busywork, it is the only way to know afterward that you did not break anything. Claude Code is good at writing these tests because it can read the current implementation and generate tests that pin down current behavior, including the weird edge cases nobody remembers the reason for.

Step 1: Scope the refactor before writing a line of code

The single biggest failure mode with agentic refactoring is an unscoped prompt like "clean up the auth module." Claude Code will happily rename things, restructure directories, and touch files you did not expect, because nothing told it not to.

Instead, start every large refactor with a planning pass that produces a written plan, not code:

claude
> I want to migrate all API route handlers in src/api from the old
  `(req, res)` Express-style signature to the new async handler
  wrapper in src/lib/handler.ts. Do not write any code yet.
  First, find every file that needs to change, group them by
  risk (has tests / no tests / touches payments), and propose
  an order of operations. Write the plan to REFACTOR_PLAN.md.

This does three things. It forces Claude Code to build a complete file inventory using grep and glob rather than guessing. It surfaces the risky files (payments, auth, anything untested) so you can decide to hand-review those instead of batch-approving them. And it gives you, the human, a plan you can actually read and edit before any code changes, which is much cheaper to fix than a 40-file diff.

If the repo has a project skill or planning workflow already set up (an everything-claude-code:planner style agent, or a plan mode), use it here. The point is the same regardless of tooling: plan first, scope explicitly, and get the plan reviewed as a design decision, not as a code review.

Step 2: Use a worktree, not your working branch

Large refactors touch a lot of files over a long session. If Claude Code is running directly against your normal working directory, one bad turn can leave you with a half-migrated tree that is hard to reason about or bisect. Git worktrees solve this cleanly: you get an isolated checkout on its own branch, so your main working copy stays untouched while the agent works.

git worktree add ../teachyou-refactor-auth -b refactor/auth-handler-migration
cd ../teachyou-refactor-auth
claude

Now the agent operates in a sandbox that is still a real, buildable copy of the repo. If the refactor goes sideways, you delete the worktree and the branch, and your main tree was never at risk:

cd ..
git worktree remove teachyou-refactor-auth --force
git branch -D refactor/auth-handler-migration

For a genuinely large migration (hundreds of files, multiple days of work), consider one worktree per subsystem so you can land pieces independently instead of holding one giant branch open. Smaller, mergeable increments beat one heroic PR every time, both for reviewers and for the agent itself, since a smaller diff is easier for Claude Code to keep consistent.

Step 3: Batch the mechanical part, hand-check the risky part

Not all refactors deserve the same level of oversight. Split the work into two buckets.

Mechanical, low-risk changes: renaming a function across the codebase, updating an import path after moving a file, converting var to const/let, updating a deprecated API call to its replacement with a 1:1 signature match. These are good candidates for Claude Code to do in bulk, because the change is deterministic and verifiable by the compiler or type checker alone.

> Every file that imports `formatDate` from `src/utils/date.ts` needs
  to import it from `src/utils/formatting/date.ts` instead. Update all
  import statements. Do not change any other code. Run `tsc --noEmit`
  after and fix any resulting errors, then stop and show me the diff.

Notice the shape of that prompt: a narrow, mechanical instruction, a stated boundary ("do not change any other code"), and a verification step baked in (tsc --noEmit) before it reports back. That verification step matters more than the instruction itself. Without it, the agent has no way to know it broke something two directories away.

Risky, behavior-bearing changes: anything touching money, auth, data migrations, or business logic without test coverage. For these, don't ask for a full-file batch edit. Ask for one file or one module at a time, and require the agent to explain what changed and why before you approve the next one. Slower, but this is where the actual judgment calls live, and judgment calls are exactly where an agent following a broad instruction can quietly make the wrong call.

Step 4: Delegate the search, keep the decisions

Claude Code's real leverage on refactors is search and mechanical execution across scale a human would find tedious. Use it for the inventory, not just the edit.

> Search the entire src/ tree for every place that constructs a
  Stripe customer object directly (not through src/lib/billing.ts).
  List file:line for each, and tell me if there's a reason it
  can't go through the shared helper (e.g. it needs a field the
  helper doesn't support).

This kind of pass often turns up things a spec-driven refactor would have missed entirely, like three call sites nobody remembered existed. Getting that inventory right before touching code is worth more than the code changes themselves, because an incomplete refactor (some sites migrated, some not) is often worse than no refactor: it leaves the codebase in two inconsistent styles that both look intentional.

For codebases too large for a single Claude Code session's context, fan the search out. Spin up multiple agents in parallel, each scoped to one directory or subsystem, each reporting back a plain-text inventory rather than making edits. Merge the inventories yourself, then run the actual edit pass in a single, sequential session so the changes stay consistent in style across the whole tree. Running edits in parallel across overlapping files is how you get merge conflicts and half-applied renames; running the search in parallel is safe because search has no side effects.

Step 5: Verification is the refactor, not a step after it

A refactor is not done when the code compiles. It's done when you can show the behavior didn't change. What "verify" means depends on what exists in the repo:

If there's an existing test suite, run it after every batch, not just at the end:

npm test -- --run
npm run typecheck
npm run lint

Ask Claude Code to run these itself and fix failures in the same turn, rather than reporting "done" and leaving you to discover a red test suite later. A good instruction pattern:

> After each file you migrate, run `npm test -- <affected test file>`.
  If it fails, fix the migration, not the test, unless the test itself
  is asserting the old behavior we're intentionally changing (in which
  case tell me explicitly before touching it).

That last clause matters. An agent under pressure to make tests pass will sometimes edit the test instead of the code. Explicitly forbidding that, except with your sign-off, closes a real failure mode.

If there's no test suite for the area being touched, write characterization tests first, as their own commit, before the refactor commit. Claude Code can generate these by reading the current implementation and asserting on its current outputs, including quirky edge cases. Once those tests are green against the old code, run the refactor, and the same tests are now your regression guard.

For anything user-facing (an API contract, a UI flow, a payment path), add an end-to-end check that exercises the real system, not just unit tests around it. A refactor of route handlers can pass every unit test and still change response headers or error codes in a way that breaks a client. Driving the actual running app, hitting the actual endpoint, and diffing the actual response catches classes of bugs that mocked unit tests structurally cannot.

Step 6: Review the diff like a diff, not like a wall of text

Even a well-scoped refactor produces a large diff. Reviewing it line by line is how reviewers rubber-stamp bugs, because after file 30 of 200 nobody is really reading anymore. Two techniques help.

First, ask Claude Code to summarize the diff by category before you open it: which files were pure renames, which had logic changes, which added new error handling. This lets you spend your attention on the 5% of files that actually changed behavior instead of spreading it evenly across all of them.

> Summarize the current diff. Group files into: pure rename/move
  (no logic change), mechanical signature update, and files where
  you made a judgment call about behavior. For the last group,
  explain the judgment call in one sentence each.

Second, keep commits small and typed by category, so git log itself becomes documentation of the refactor's shape:

git commit -m "refactor: rename formatDate import paths (mechanical, no logic change)"
git commit -m "refactor: migrate auth handlers to async wrapper (logic preserved, verified via existing auth.test.ts)"

This is worth the discipline even under time pressure. A refactor branch with 40 well-labeled small commits is reviewable and revertible piece by piece. One giant commit is neither.

Common failure modes and how to avoid them

The agent "fixes" something outside scope. This happens when the prompt is broad and the agent notices something adjacent that looks wrong. Always state the boundary explicitly ("touch only files under src/api/routes, do not modify src/lib") and ask it to flag out-of-scope issues rather than fix them inline. Most agent harnesses have a mechanism for exactly this: flag it, don't fix it silently.

Green build, broken runtime. Type checkers and linters catch a narrow slice of bugs. A refactor that changes async timing, error propagation, or default parameter behavior can pass tsc and eslint cleanly while changing what the code actually does at runtime. This is why step 5 insists on running the app, not just the type checker.

Refactor scope creep across a long session. The longer an agent session runs, the more likely it drifts from the original plan as it accumulates context and starts making calls that felt reasonable in the moment but weren't part of the brief. Re-anchor periodically: paste the original plan back in and ask "are we still following this, and if not, why did we deviate."

No rollback plan. Before starting, know how you'd revert if the refactor goes wrong in production. A worktree-and-branch workflow makes this trivial at the git level, but also confirm the deploy story: can you roll back a database migration that shipped alongside the code refactor, or is that a one-way door. Ask this question before the refactor starts, not after something breaks.

A minimal end-to-end example

Putting the steps together for a concrete, small-scale example: migrating a project off a deprecated logging library.

# 1. Isolate the work
git worktree add ../proj-logging-migration -b refactor/logging-migration
cd ../proj-logging-migration

# 2. Plan and inventory (no code changes yet)
claude
> Find every file that imports from `old-logger`. Group by whether
  they use structured logging (fields object) or plain string
  logging. Write the plan to REFACTOR_PLAN.md, don't edit code yet.

# 3. Characterization tests for anything untested
> For files with no existing test coverage that call old-logger,
  write a minimal test asserting current log output shape.

# 4. Mechanical migration, verified per batch
> Migrate the plain string logging call sites first (lower risk).
  After each file, run `npm run typecheck`. Show me the diff when
  the batch is done, don't commit yet.

# 5. Review, commit, repeat for structured logging call sites
git add -A && git commit -m "refactor: migrate plain-string log calls to new-logger"

# 6. Full verification before merging
npm test -- --run
npm run build

The pattern generalizes to migrations far bigger than a logging library: framework upgrades, ORM swaps, monorepo splits. The scale changes, the discipline does not.

FAQ

Can Claude Code refactor an entire codebase in one prompt? Technically it can attempt to, but you should not let it try on anything nontrivial. Large, unscoped refactor prompts produce large, unreviewable diffs and increase the odds of an out-of-scope change slipping through. Break the work into a planning pass, then small, independently verifiable batches.

How do I stop Claude Code from touching files outside the refactor's scope? State the boundary explicitly in the prompt (which directories or files are in scope) and instruct it to flag anything else it notices rather than fix it inline. Reviewing the diff by file path before committing catches anything that slipped past the instruction.

What if the codebase has no tests at all? Write characterization tests against the current behavior before refactoring anything. Claude Code can generate these by reading the existing implementation and asserting on its current outputs. Once they're green against the old code, they become your regression guard for the refactor.

Is a git worktree necessary, or can I just refactor on a normal branch? A normal feature branch works for small refactors. For anything spanning many files or multiple sessions, a worktree keeps your main working copy untouched and makes an abandoned attempt a simple git worktree remove, rather than a git reset --hard that risks losing unrelated in-progress work.

How do I verify a refactor actually preserved behavior, beyond the build passing? Run the existing test suite after every batch, not just at the end, and for anything user-facing, exercise the real running app end to end rather than relying on unit tests alone. A compiler catches type mismatches, not behavior changes, so treat "it builds" as a floor, not a finish line.

Should I let Claude Code fix a failing test by editing the test itself? Only with explicit permission. Instruct it to fix the migrated code to satisfy the existing test, and to flag (not silently edit) any case where the test seems to be asserting old behavior you're intentionally changing. Letting an agent edit tests to make them pass defeats the purpose of having tests as a safety net.

How do I keep a long refactor session from drifting off the original plan? Periodically paste the original plan back into the conversation and ask whether the recent changes still match it. Long sessions accumulate context, and an agent can start making reasonable-sounding calls that quietly expand scope. Re-anchoring catches this before it compounds across dozens of files.