Claude Code multi-file refactor: how to restructure a codebase without breaking everything
Refactoring a large codebase is where most developers hesitate to use AI. Too many files. Too many dependencies. Too easy to break something in file C when you're editing file A.
Here's the systematic approach that actually works with Claude Code.
The problem with naive refactoring
Asking Claude Code "refactor this codebase" is asking for trouble. You'll get:
- Changes that break imports in files Claude didn't read
- Renamed functions that still have the old name in 6 other places
- New patterns introduced inconsistently across files
The solution is structured multi-file context management.
Step 1: Map before you move
Before touching a single file, ask Claude Code to build a dependency map:
> Read the src/ directory structure. List every file, what it exports, and what it imports. Don't change anything yet.
Claude will read each file and build a mental model. This is your safety net.
Save it:
> Write that dependency map to REFACTOR_PLAN.md. Include: file path, exports, imports, and any circular dependencies.
Step 2: Define the target architecture
Now describe what you want the end state to look like:
> I want to reorganize this into:
- /src/models/ — pure data types, no side effects
- /src/services/ — business logic, one file per domain
- /src/routes/ — HTTP handlers only, thin wrappers
- /src/utils/ — shared helpers
Update REFACTOR_PLAN.md with the target location for each current file.
Claude will reason through the mapping and flag conflicts before you've changed anything.
Step 3: Refactor in layers, not all at once
The key insight: refactor one layer at a time, test between each layer.
> Start with models only. Move the types from src/database.js to src/models/user.js and src/models/post.js. Update all imports. Run tests after each file move.
Never ask Claude to refactor multiple layers simultaneously. The blast radius is too large.
Step 4: The import update pattern
The most common refactor failure is missed imports. Use this pattern:
> After moving [filename], grep the entire codebase for any import of [old-path] and update each one. Show me every file you changed.
This explicit grep-then-update command catches the stragglers that break at runtime.
Step 5: Run tests after every file move
Don't batch up 10 moves and test at the end. Test incrementally:
> Move src/auth.js to src/services/auth.js. Update imports. Run `npm test`. If tests fail, stop and show me the errors before continuing.
The "stop and show me" instruction is critical. Without it, Claude may attempt to fix failures silently and create cascading problems.
Handling circular dependencies
Claude Code is excellent at identifying and breaking circular dependencies:
> I see that services/user.js imports from services/post.js and post.js imports from user.js. Propose 3 ways to break this circular dependency without changing the external API.
Claude will reason through the options. Pick one, then:
> Implement option 2. Create the shared types in models/shared.js first, then update both service files.
The CLAUDE.md checkpoint pattern
For multi-session refactors, maintain a checkpoint file:
# REFACTOR STATUS
## Completed
- [x] models/user.js — moved, imports updated, tests passing
- [x] models/post.js — moved, imports updated, tests passing
## In Progress
- [ ] services/auth.js — moved, imports NOT YET updated
## Blocked
- services/billing.js — circular dep with services/user.js, needs design decision
## Next Session
Start with: resolve billing.js circular dependency (see REFACTOR_PLAN.md line 47)
At the start of each session:
> Read REFACTOR_PLAN.md and REFACTOR_STATUS.md. Continue from where we left off.
Claude picks up exactly where you stopped.
When to use --dangerously-skip-permissions
For large refactors, you'll hit permission prompts constantly. In a sandboxed environment:
claude --dangerously-skip-permissions
This lets Claude move files, create directories, and update imports without interrupting you for every operation. Only use this in repos where you have clean git state (so you can roll back).
Real example: extracting a monolith
Here's a sequence that moved a 3,000-line server.js into a proper service architecture:
> server.js is 3,000 lines. Read it. Identify all logical domains (auth, payments, users, notifications). List them.
> Create the directory structure for these domains under src/services/. Empty files only.
> Extract the auth functions (lines ~200-450) into services/auth.js. Update server.js to import from there. Run tests.
> Extract the payment functions. Run tests.
[repeat for each domain]
> server.js should now be thin route handlers only. Verify it's under 300 lines. Show me what's left.
Total time: 45 minutes. Zero test failures at the end.
The rate limit problem at scale
Large refactors are where Claude Code's rate limits hit hardest. You're reading many files, writing many files, running tests repeatedly. A single large refactor session can hit the limit mid-operation — exactly when you don't want to stop.
The fix developers use: set ANTHROPIC_BASE_URL to a proxy that removes per-session limits.
export ANTHROPIC_BASE_URL=https://simplylouie.com
SimplyLouie is ✌️$2/month and removes the rate limit interruptions during long refactor sessions. 7-day free trial, no charges until day 8.
Summary: the refactor protocol
- Map first — dependency map before any changes
- Plan in CLAUDE.md — write target architecture, let Claude verify feasibility
- Layer by layer — models first, then services, then routes
- Test after every file move — not after 10 moves
- Explicit import grep — always ask Claude to grep for old paths after moves
- Checkpoint file — for multi-session refactors, maintain REFACTOR_STATUS.md
- Clean git state — commit before refactor, commit after each layer
The difference between a successful large-scale refactor and a week of debugging is almost always process. Claude Code is capable of handling it — it just needs a structured protocol to work from.
Top comments (0)