Refactoring is where most developers discover Claude Code's real limitations — and its real strengths. Sent into a large file without context, Claude hallucinates method names and misses side effects. Given the right structure, it can refactor a 600-line god component in under ten minutes while keeping every test green.
The difference is entirely in the workflow.
Why Refactoring Is Hard for AI
A fresh context window knows nothing. It doesn't know that UserService calls AuthService under the hood. It doesn't know the database expects snake_case. It doesn't know you migrated from Redux to Zustand six months ago.
This is exactly why refactoring fails out of the box: you hand Claude a file, ask it to clean it up, and it invents new abstractions that break three other files you didn't show it.
The fix is context — but not context in the chat window. Context in your codebase.
Rule 0: Always Read Before Changing
Before asking Claude to refactor anything, make sure it has read:
- The file being changed
- Every file that imports from it
- Every file it imports from
- Relevant types or interfaces
# Instead of: "refactor UserService"
# Do this first:
claude "Read src/services/UserService.ts, src/types/user.ts, and every file
that imports UserService. Once you understand the full picture, summarize
what UserService does and what would break if we changed its interface."The summary step is not optional. It forces Claude to actually read instead of pattern-matching on the filename.
CLAUDE.md as Your Refactoring Brief
If you're doing a large refactoring effort that spans multiple sessions, your CLAUDE.md is the place to encode the rules.
# CLAUDE.md — Refactoring Context
## Current goal
Splitting the monolithic UserController into:
- UserAuthController (login, register, token refresh)
- UserProfileController (read/update profile)
- UserAdminController (admin-only operations)
## Rules for this refactor
- Keep all existing HTTP endpoints — external clients depend on them
- New services are injectable (constructor injection, not singletons)
- All database calls go through the repository layer, not direct Prisma calls
- Error responses follow the existing ErrorResponse type in src/types/errors.ts
## Already done
- UserAuthController: done, tests passing
- UserProfileController: done, tests passing
- UserAdminController: in progress
## Do not touch
- src/middleware/auth.ts — frozen until v2
- src/types/ApiResponse.ts — external contractThis pays off immediately. Every new session starts knowing where you are, what the rules are, and what not to break. You skip five minutes of re-explaining the codebase at the start of each task.
The Four-Step Refactoring Workflow
Step 1: Understand
Never start with a change. Start with a question.
"Read src/components/UserDashboard.tsx and its imports. Tell me:
- What does it currently do?
- What are its dependencies?
- What state does it manage?
- What would be risky to move?"
The answer tells you two things: whether Claude actually read the file, and whether your mental model matches reality.
Step 2: Plan
Ask Claude to propose the refactoring before writing a single line.
"Based on what you've read, propose how you'd split UserDashboard into
smaller components. List each new component, what it would contain,
and what props it would need. Don't write any code yet."
Review the plan. This is where you catch bad ideas before they're in the code. If the plan looks right, approve it and move to execution.
Step 3: Execute in Chunks
Never ask Claude to refactor an entire large file in one shot. Break it into vertical slices.
"Implement step 1 from the plan: extract UserDashboardHeader into
its own file at src/components/UserDashboardHeader.tsx. Keep the
logic identical — no behavior changes, just extraction. Update
the import in UserDashboard.tsx."
One thing at a time. One file changed, one file updated. After each chunk, run your tests. This is where most people go wrong — they ask Claude to "refactor the whole thing" and then spend an hour debugging when three things break at once.
Step 4: Verify
"The tests are passing. Now review the diff of what changed.
Does UserDashboard.tsx still have any logic that belongs in
UserDashboardHeader? Is there anything we missed?"
The review step catches regressions that tests don't cover — subtle prop renames, missing aria-label, changed behavior that still compiles.
Handling Large Files
Files over 400–500 lines hit a practical problem: Claude reads them, but the beginning is compressed by the time it's writing changes at line 450. Context management matters a lot here.
Strategy A: Read sections, not the full file
"Read lines 1-150 of src/services/OrderService.ts.
Tell me what those lines do and what functions they define."
Work section by section. Use /compact between major chunks to keep the context clean.
Strategy B: Extract the interface first
"Read src/services/OrderService.ts and extract only the public
method signatures (not the implementations) into a summary.
We'll use this as our working reference while we refactor."
Now you have a lightweight map of the file in context that you can reference without re-reading the whole thing each time.
Renaming at Scale
Renaming a type, function, or variable across a large codebase is one of the safest tasks for Claude Code — if you approach it correctly.
"I need to rename `userId` to `user_id` across the repository
to match our database naming convention.
1. First, use grep to find every file that uses `userId`
2. List them — don't change anything yet
3. I'll tell you which files to update"
The list-first step is important. Blindly changing every occurrence will catch false positives — comments, test fixtures with intentional names, generated code you shouldn't touch.
For TypeScript renames, Claude can also catch places where the old name was inferred:
"After renaming the interface field, check if there are any
destructuring patterns like `const { userId } = user` that
TypeScript wouldn't catch automatically if we only changed the type."
Extracting Services from God Components
The most common real-world refactoring: a React component that has grown to 500+ lines and does everything — data fetching, business logic, state management, rendering.
The extraction order matters:
1. Extract types first. Move interfaces and types to a types.ts. Zero risk, zero behavior change.
2. Extract pure functions. Any function that doesn't touch state or refs can be moved to utils.ts immediately.
3. Extract hooks. Any useState/useEffect cluster that belongs together becomes a custom hook. This is where the real complexity reduction happens.
4. Extract sub-components. Once the hooks are clean, the JSX sections that consume them become obvious component boundaries.
Each step is a separate Claude session with a separate commit. Never combine steps.
"We're on step 3: extracting hooks from UserDashboard.tsx.
Read the file. Identify all useState/useEffect clusters that
could become a custom hook. Propose hook names and what each
would contain. Wait for my approval before writing any code."
Keeping Tests Green
Run tests after every step. Not after every session — after every step.
TDD workflow with Claude Code goes deeper on this, but the refactoring-specific rule is: if your tests don't cover the code you're refactoring, write characterization tests before you start.
"Before we refactor UserService, write tests that cover its
current behavior. I don't care if the tests are ugly — I just
need a safety net that will tell me if the refactor changes
any observable behavior."
These are throwaway tests. They exist to catch regressions. After the refactor is done, you replace them with proper tests for the new structure.
Common Pitfalls
Pitfall 1: Showing only the file you're refactoring
Claude doesn't know what imports the file. It refactors it perfectly in isolation, then you discover the interface changed and three callers are broken.
Fix: Always show callers when refactoring public interfaces.
Pitfall 2: Long sessions for large refactors
Context degrades. After 90 minutes on a complex refactoring session, Claude starts forgetting rules from CLAUDE.md and re-introducing patterns you already corrected.
Fix: Use /compact aggressively between steps. Break multi-day refactors into sessions, one per logical step. See the /compact vs /clear guide for the full breakdown.
Pitfall 3: Approving the plan without reading it
The plan step is not bureaucracy. It's where you catch architectural mistakes before they're in code. Take 60 seconds to actually read what Claude proposed.
Pitfall 4: Not committing between steps
Every successful step deserves a commit. If step 4 breaks everything, you want to git revert to the working state after step 3, not start from scratch.
A Real Example: Splitting a God Service
Here's a realistic sequence for splitting a 700-line OrderService.ts:
Session 1:
"Read OrderService.ts and all files that import from it. Summarize
what the service does and identify natural domain boundaries."
→ Claude identifies: payment logic, inventory logic, notification logic
Session 2 (after /clear):
"Read OrderService.ts. Extract all payment-related methods into
PaymentService.ts. Methods to move: [list from session 1 summary].
Keep OrderService calling PaymentService — no interface changes for callers."
→ Commit: "refactor: extract PaymentService from OrderService"
Session 3 (after /clear):
"Read OrderService.ts. Extract inventory methods: [list].
Same pattern as PaymentService."
→ Commit: "refactor: extract InventoryService from OrderService"
Each session is focused, clean, and committed. Debugging becomes much simpler when you have small atomic commits instead of one massive "refactored everything" commit.
The Rule of One
Refactoring with Claude Code comes down to one principle: one thing at a time.
One file read. One change proposed. One step executed. One test run. One commit.
The urge to "just do the whole thing" is exactly what leads to sessions where you've lost track of what changed, tests fail in three places, and you're debugging Claude's refactoring instead of shipping features.
Slow down the individual steps. Speed up the total time.