Skip to content

Multi-File Workflows

You need to rename a field that’s threaded through a dozen files — the model, the service, three controllers, the migration, the API contract, and a wall of tests. Do it by hand and you’ll miss two callers and ship a 500. This is exactly the kind of change Claude Code is built for: it traces the dependency graph, edits every file consistently, and updates the tests in the same pass. The trick is knowing how to drive it so it stays accurate across the whole blast radius.

  • A plan-first prompt that forces Claude to map the blast radius before it touches a single file
  • A repeatable pattern for safe, backward-compatible field and API renames
  • Copy-paste prompts for cross-cutting refactors (extract shared logic, type propagation, dependency tracing)
  • The real way to run independent multi-file tasks in parallel using git worktrees
  • A recovery playbook for when a multi-file edit goes wrong mid-flight

The single biggest accuracy win on multi-file changes is to separate planning from editing. Run Claude in plan mode first (claude --permission-mode plan, or press Shift+Tab to toggle into it), let it explore and propose, then approve before any file is written.

  1. Ask for the change plan, not the change. Claude explores the codebase and reports what it intends to touch — without editing.

  2. Review the blast radius. You’ll catch missing files or wrong assumptions here, where they’re free to fix, instead of in a diff of 40 files.

  3. Refine, then approve. Add anything it missed, then let it execute the approved plan one layer at a time.

Plan mode is read-only by design, so this is safe to run even on main. When the plan looks right, approve it and Claude switches to execution.

Before a rename or schema change, make Claude surface everything that depends on the thing you’re about to move. This is faster and more reliable than grep because it follows imports and types, not just text.

Use the returned checklist as the contract for the edit pass. If Claude later edits a file that wasn’t on the list, that’s your signal to pause and re-check the plan.

A rename looks trivial until it’s a public field that external clients depend on. The pattern that survives production is additive-then-deprecate, not rip-and-replace.

  1. Add the new name alongside the old. Keep both working so nothing breaks on deploy.

  2. Migrate internal callers to the new name while the API still accepts the old one.

  3. Deprecate the old name with a warning so you can see remaining usage.

  4. Remove the old name once telemetry confirms no active callers.

For typed languages, lean on Claude’s ability to propagate types instead of chasing compiler errors by hand:

When logic is duplicated across controllers or services, ask Claude to consolidate it while preserving the existing public interfaces — the consolidation should be invisible to callers.

Extract the validation logic duplicated in UserController, OrderController, and
ProductController into a single ValidationService. Keep each controller's existing
method signatures unchanged. Add unit tests for the extracted service.

When a refactor touches an MCP-connected system — for example renaming a column that exists in your actual database — connecting the Postgres MCP server lets Claude read the live schema instead of guessing from migrations. See the refactoring patterns lesson for that workflow.

For large changes, drive Claude through the stack in order rather than asking for everything at once. Narrow, sequential prompts keep context focused and make each diff reviewable.

1. "List every file that needs to change for user roles, grouped by layer."
2. "Implement the database migration and model changes only."
3. "Now the role-check middleware and the protected routes."
4. "Now the role-management UI."
5. "Now update and run the tests."

Between unrelated phases, run /clear to drop stale context, or /compact Focus on the role-check seam and the files we've already changed to summarize while keeping what matters. Use /context to see what’s actually consuming the window, and /cost to watch spend. These are real interactive slash commands — run them inside the claude REPL, not as terminal flags.

If you have several genuinely independent streams of work — a new auth system, a payments rewrite, an analytics pass — run each in its own git worktree so they get isolated working directories and branches. This is the documented way to parallelize, and it keeps each task’s context clean.

Terminal window
# One worktree (and branch) per stream of work
git worktree add ../feature-auth feature/authentication
git worktree add ../feature-payments feature/payments
git worktree add ../feature-analytics feature/analytics
# Launch Claude Code in each worktree (separate terminals or tmux panes)
tmux new-session -d -s auth 'cd ../feature-auth && claude'
tmux new-session -d -s payments 'cd ../feature-payments && claude'
tmux new-session -d -s analytics 'cd ../feature-analytics && claude'

Each session is its own conversation against its own checkout, so the edits never collide. When you’re done with a tree, git worktree remove ../feature-auth.

Choose the right parallelism mechanism:

MechanismBest for
Git worktrees + one claude per treeIndependent tasks on separate branches that must not touch each other’s files.
SubagentsSpecialized helpers inside one session (clean sub-context, fast handoffs) — launch with the --agents flag or define them in .claude/agents/.
Agent teamsMultiple agents that coordinate and message each other; set --teammate-mode and enable with CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1.

If you instead need Claude to read across several directories in a single session (a monorepo app plus a shared lib), don’t use worktrees — add the directories with --add-dir:

Terminal window
claude --add-dir ../apps ../lib "Update the shared logger and every app that imports it"

Claude works one repository at a time, so cross-repo changes are sequential. Drive the producer first, then each consumer, and pin the contract in each repo’s CLAUDE.md so future sessions know what changed.

Terminal window
cd api-service
claude "Update the /users endpoint to return clientId instead of customerId,
but keep accepting customerId on input for one release."
cd ../frontend-app
claude "The /users API now returns clientId (customerId is deprecated).
Update all calls and data handling to read clientId."
cd ../mobile-app
claude "Update to read clientId from the /users API, keeping a fallback to
customerId so older app builds keep working."

Record the migration in each repo so the next session has the context:

<!-- in each repo's CLAUDE.md -->
## Recent migrations
- clientId replaces customerId in /users responses; customerId still accepted on input until v3.

Multi-file edits fail in predictable ways. Here’s how to catch and recover from each.

When an edit pass goes wrong, git is your undo:

Terminal window
git status # see everything that changed
git diff # review before trusting anything
git checkout -- path/to/file.ts # revert a single file
git reset --hard HEAD # nuke all uncommitted changes and start over

For a partial recovery — keep the good, drop the broken — describe it precisely:

Commit in logical phases as you go (after the migration, after the middleware, after the UI). Small, frequent commits turn “the whole change is broken” into “the last commit is broken.”