Multi-file workflows
A multi-file workflow in Claude Code separates three jobs: plan mode maps every file a change touches before any edit, a list of acceptance checks defines “done” first, and the edits run one layer at a time, fanned out with /batch or --worktree only when the units are independent. The checks, not a line-by-line read, prove the result.
You need to rename customerId to clientId. It runs through the model, a serializer, three controllers, a migration, the public API contract, and 60 tests. A single “rename it everywhere” prompt returns a 40-file diff that looks plausible, and nobody on the team can say whether a mobile client that still sends customerId breaks tomorrow. This page is for the developer who drives that change and the tech lead who has to approve it without reading all 40 files.
What a multi-file change workflow gives you
Section titled “What a multi-file change workflow gives you”- A plan-first prompt that makes Claude Code list the blast radius, grouped by layer, before it edits anything.
- An acceptance-check list you write before the edit pass, so the result is proven by commands, not by reading.
- A backward-compatible rename pattern (expand, migrate, contract) that survives clients you do not control.
- A rule for choosing between one session,
/batch, parallel worktrees, subagents, and--add-dir. - A recovery table for the ways multi-file edits fail, with the command that gets you out of each.
Which Claude Code mechanism fits your multi-file change?
Section titled “Which Claude Code mechanism fits your multi-file change?”Choose the mechanism by how independent the pieces are, not by how many files there are. A 40-file rename is one tightly coupled change; three unrelated features are three changes.
| Your change | Use | Why |
|---|---|---|
| One coupled change across layers (rename, new field, new role check) | One session: plan mode, then a layer-by-layer edit pass | Every edit depends on the previous one; splitting it creates merge conflicts and inconsistent names. |
| The same mechanical edit in many independent places (migrate 25 call sites to a new API) | /batch | The bundled skill splits the change into 5 to 30 units and runs each in a background subagent in its own worktree. |
| Several unrelated streams (auth, payments, analytics) | One claude --worktree <name> session per stream | Each session gets its own checkout and branch, so edits never collide. |
| Research before the edit (who calls this, what breaks) | A subagent, for example the built-in Explore agent | The search results stay out of your main context window. |
| One change across sibling directories (a shared library and the app that imports it) | --add-dir in a single session | Claude Code can read and edit both directories with one plan. |
Agent teams, a lead session coordinating peer sessions with shared tasks, are experimental and off by default (CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1). Reach for them only after the options above stop fitting.
Plan the blast radius before any edit
Section titled “Plan the blast radius before any edit”A missed file costs one line to fix in a plan and a debugging session to find in a 40-file diff, so force the plan before the first write. From Claude Code v2.1.283 on the latest channel, interactive sessions start in auto mode, which edits files without stopping to ask (claude -p, the Agent SDK, sessions where a settings file sets disableAutoMode, and models auto mode does not support still start in Manual). Switching to plan mode is therefore an explicit step, not the default.
-
Start the session in plan mode, or switch an open session into it with Shift+Tab or
/plan:Terminal window claude --permission-mode plan -
Ask for the plan, not the change. Plan mode is read-only, so Claude Code explores and proposes without writing a file.
-
Compare the file list against what you know. A missing layer (the mobile client, a CSV export, a background job) is free to fix here and expensive to find in a diff.
-
Save the approved plan to a file in the repo, for example
plans/client-id-rename.md. The file list in it becomes the contract for the edit pass and for review.
Tracing by imports and types, not only text, is the reason to ask the agent instead of running grep: a text search misses the destructured const { customerId: id } and the generic pick(user, fields) call. Section 5 of the prompt matters as much: it tells you what the plan does not cover.
Write the acceptance checks before the edit pass
Section titled “Write the acceptance checks before the edit pass”A multi-file diff is too large to verify by reading. Decide what proves it correct before Claude Code writes a line, and make each check a command that exits non-zero on failure. Typical checks for a rename:
- The type-checker and linter pass (
npm run typecheck,npm run lint, or your stack’s equivalents). - The existing test suite passes without edits to assertions that are unrelated to the rename.
- A search for the old name returns only the lines the plan allows (the input alias and the deprecation log).
- A new test proves that a request that still sends
customerIdgets the same response as before.
The last check is the one that catches the production outage, and it has to exist before the change so that it can fail. See how to write acceptance criteria an agent can check and how to keep the agent from editing the tests that judge it.
Rename a public field without breaking callers
Section titled “Rename a public field without breaking callers”A rename is internal until one external client depends on the name. For anything the plan marked PUBLIC, use expand, migrate, contract instead of replace:
-
Expand. Produce the new name everywhere and accept both names on input, mapping
customerIdtoclientId. -
Migrate. Move every internal caller to the new name while the API still accepts the old one.
-
Warn. Log a deprecation line each time an inbound request still uses the old name, so you can see who still sends it.
-
Contract. Remove the old name once the logs show no traffic for an agreed period, in a separate change.
The “not in plan” rule is the cheapest regression detector you have. An unplanned edit means either the plan was incomplete or the agent is wandering, and you want to know which before the next layer builds on it. For deeper rename, extract, and restructure recipes, see refactoring patterns; for schema changes that need a data migration, see database migrations.
Execute one layer at a time and commit between layers
Section titled “Execute one layer at a time and commit between layers”Large changes stay accurate when each prompt covers one layer: the migration and models, then services, then routes, then UI, then tests. Each layer is a diff small enough to check, and each commit is a known-good point to return to.
Manage context between layers with the interactive commands:
/contextshows what is filling the context window./compact Keep the plan file path, the acceptance checks and the list of files already changedsummarises while keeping what the next layer needs./clearstarts fresh; re-anchor by pointing Claude Code at the plan file./usageshows session cost and plan limits./rewind, or Esc twice on an empty prompt, returns code and conversation to an earlier checkpoint.
Commit after each layer passes its checks. “The whole change is broken” then becomes “the last commit is broken”, and git revert of one commit replaces an afternoon of untangling.
Fan out a mechanical change with /batch
Section titled “Fan out a mechanical change with /batch”When the plan shows the same edit in many places that do not depend on each other, such as moving 25 route handlers from a deprecated logger to a new one, a single session wastes time doing them in sequence. The bundled /batch skill decomposes the change into 5 to 30 units and runs each in a background subagent in its own worktree.
/batch suits edits whose correctness you can check per unit. It is the wrong tool for the clientId rename above, where every unit depends on the model change landing first. For many unrelated tasks across a repository, not one change, see batch operations.
Run independent streams in parallel worktrees
Section titled “Run independent streams in parallel worktrees”For unrelated streams of work, give each its own session and checkout. --worktree (-w) creates a git worktree and branch for the session, and --tmux opens it in its own tmux session (it uses iTerm2 native panes when available; --tmux=classic forces plain tmux):
# In a terminal at the repository root: one session, worktree and branch per streamclaude --worktree auth --tmuxclaude --worktree payments --tmuxclaude --worktree analytics --tmuxIn Claude Code 2.1.283 the worktrees live under .claude/worktrees/, so add that path to .gitignore if your repository does not ignore it yet. Worktrees isolate files and branches, not everything else: every checkout still reaches the same development database, ports, cloud account, and secrets. Give each stream its own port block and local data, and keep remote migrations and deploys out of agent sessions. The parallel worktrees guide covers port assignment and integration order, and tmux for agent fleets covers watching several sessions at once.
Change a shared library and its consumers
Section titled “Change a shared library and its consumers”When one change spans a shared library and the apps that import it, keep it in one session so one plan covers both. Start the session with the extra directories, then type the prompt:
# In a terminal inside apps/webclaude --add-dir ../../packages/shared-loggerAcross separate repositories, work producer first, then each consumer, one session per repository. Keep the expand step in the producer until every consumer has shipped, and record the migration in each repository’s CLAUDE.md so the next session does not reintroduce the old name:
<!-- CLAUDE.md in each consuming repository -->## Active migrations- /users returns `clientId`; `customerId` is still accepted on input until v3. Do not add new reads of `customerId`.See the memory system for where that note belongs and how Claude Code loads it.
How do you verify a 40-file diff without reading every line?
Section titled “How do you verify a 40-file diff without reading every line?”You verify the change against the plan and the checks, and you read only where they point. Run this sequence after the last layer:
-
Scope.
git diff --stat main...HEADagainst the plan’s file list. Every file outside the plan needs a reason. -
Gates. Run the acceptance checks from the plan yourself, in a clean shell, not only inside the session. A hook that runs the type-checker after each edit catches breakage per layer instead of at the end.
-
Behaviour. The compatibility test written before the change must still pass, unmodified. Check with
git log -pon that test file. -
Independent review. Run
/code-reviewon the branch, ideally from a fresh session (/clearor a newclaude), so the review is not steered by the reasoning that produced the change. See review automation for running it on every pull request. -
Sign-off. The reviewer approves the plan, the check output, and the review findings, and reads only the files that a check or a finding points at. That is the shift described in evidence, not diffs.
Any “NOT COVERED” line is either a test to add or a risk the reviewer accepts in writing.
What breaks in multi-file edits, and how do you recover?
Section titled “What breaks in multi-file edits, and how do you recover?”| Symptom | Likely cause | Recovery |
|---|---|---|
| Claude Code edits a file that is not in the plan | The dependency trace missed a caller, or the agent is drifting | Stop the session with Esc. Ask it to explain the edit, then update the plan file or /rewind past the edit. |
| Type errors or missing imports after a layer | A layer changed a signature that the next layer relies on | Paste the checker output back: “Fix these errors from the rename without changing behaviour or test assertions.” |
| The old name survives in comments, fixtures or docs | The change spanned several prompts and the search was literal-only | Run the ripgrep acceptance check and ask for every remaining match to be fixed or justified. |
| Answers get vague, earlier decisions are forgotten | The context window filled over a long session | /compact with explicit keep instructions, or /clear and re-anchor on the plan file. |
| Tests pass because assertions were edited | The agent “fixed” the test instead of the code | Revert the test file (git restore --source=main -- path/to/test), then state in the prompt that assertions are read-only. |
| One stream breaks while the rest is fine | Unrelated work mixed in one session | Revert only that part, using the surgical recovery prompt below. |
For undo, prefer the smallest tool that works:
/rewind # in Claude Code: back to a checkpoint, code and conversationgit restore path/to/file.ts # in a terminal: discard uncommitted edits to one filegit revert <commit> # undo one committed layer, keeping historygit stash push -m "failed-rename-attempt" # park everything and start again from a clean treeWhere to go next with multi-file changes
Section titled “Where to go next with multi-file changes”- Before this page: feature planning turns a request into the plan this workflow executes.
- Next: refactoring patterns for extract, move, and restructure recipes.
- Working in a large codebase when the blast radius is too big to plan in one pass.
- Custom subagents to move dependency tracing and review out of your main context.
- Debugging when a multi-file change introduces a bug the checks missed.
- Cost control to keep long multi-layer sessions affordable.
- The same workflow in the other tools: Codex worktrees and Cursor multi-repo workflows.