You asked Claude Code to rename a function in billing.ts. The diff comes back with 12 files changed, 200 lines added: a “consistency pass” across 4 unrelated modules, formatting you didn’t want, and a “minor improvement” to import order in 8 files. The task was 5 minutes; review is now 30.
Fastest fix: don’t rely on the prompt alone. Add a permissions.deny rule so Claude Code physically cannot edit files outside the target, then give every prompt an explicit editable-file list and send adjacent findings to FOLLOWUPS.md instead of fixing them. Without a boundary, Claude follows code paths it considers “related”: imports lead to callers, callers lead to other modules, modules have their own inconsistencies, and your one-file task becomes a refactor.
One thing to internalize before the steps: a permissions rule is enforced by the Claude Code harness; a CLAUDE.md instruction is not. The official docs are explicit that “Permission rules are enforced by Claude Code, not by the model. Instructions in your prompt or CLAUDE.md shape what Claude tries to do, but they don’t change what Claude Code allows.” So prompt-layer fences (Steps 2-3) reduce the urge to expand; the permissions deny rule (Step 1) is the only thing that actually stops a write.
Which bucket are you in?
| Symptom in the diff | Most likely cause | Jump to |
|---|---|---|
| Rename applied in files you never mentioned | Prompt named no file boundary | Step 1, Cause 1 |
| One core change plus many small call-site edits | Agent followed import/call chains | Cause 2 |
| Import-order / formatting / naming churn unrelated to the task | Unprompted consistency pass | Cause 3 |
Broad changes that “match” a CLAUDE.md rule | A standing “clean up while you’re at it” authorization | Step 3, Cause 4 |
| The whole diff is genuinely required | Real multi-file refactor bundled into one PR | Step 5, Cause 5 |
| Many test files changed alongside the rename | Cascading “test fix” | Step 6, Cause 6 |
Common causes
Ordered by hit rate, highest first.
1. Prompt didn’t name the file boundary
“Rename getUser to findUser” doesn’t say where. Claude renamed it everywhere, including legacy code paths you’ve been migrating away from, where the rename actually broke the migration.
How to spot it: re-read your prompt. If it doesn’t list specific files, the boundary was implicit and Claude expanded.
2. Agent followed import/call chains into adjacent files
Claude renamed getUser in service.ts, then updated all 8 call sites across the codebase. Each call site is a legitimate touch, but the cumulative scope is 9x what you intended.
How to spot it: diff includes a “core” file change plus many small call-site updates. Each is necessary; together they are scope expansion.
3. Agent applied a consistency pass unprompted
Claude noticed half the files use import type and half don’t. While renaming, it “harmonized” them across 6 files. None of that was asked for.
How to spot it: diff has changes unrelated to the rename (import order, formatting, naming). These are pure scope creep.
4. CLAUDE.md or prompt authorized “clean up while you’re at it”
# CLAUDE.md
- Improve code quality where you see opportunities
- Fix obvious issues during related work
Innocent-looking rules; standing authorizations for scope expansion.
How to spot it: grep -i "improve\|clean up\|while you're\|opportunit" CLAUDE.md. Each match is a creep authorization.
5. Refactor by nature touches many files
Some refactors genuinely span the codebase (rename a public API, migrate a state library). The scope is real, but bundling it into one PR is the problem. It should have been split. Anthropic’s own guidance puts the reliable ceiling around 20-40 files in a single session; past that, split into phases with a commit between each.
How to spot it: the diff is legitimately required. The bug is “one mega-PR” instead of “PR chain.”
6. Agent’s “test fix” cascaded
Tests failed after the core change. Claude “fixed” them by updating expectations, mocks, and fixtures across many test files. Some are legit (matching the new signature); some are masking failures.
How to spot it: many test-file changes accompany the rename. Audit which ones are signature updates versus which are silently weakening assertions.
Shortest path to fix
Ordered by ROI. Step 1 is the only hard enforcement; Steps 2-3 shape behavior at the prompt layer.
Step 1: Enforce the boundary with a permissions deny rule
This is the part the original mega-diff was missing. Add an Edit deny rule to .claude/settings.local.json so Claude Code blocks any write outside the files you intend to touch, regardless of what the prompt or CLAUDE.md says. Edit rules apply to every built-in file-editing tool and to file-writing Bash commands Claude Code recognizes (sed, etc.).
{
"permissions": {
"deny": [
"Edit(/src/**)"
],
"allow": [
"Edit(/src/services/user.ts)",
"Edit(/src/services/user.test.ts)"
]
}
}
Rules evaluate deny first, then ask, then allow, so the broad Edit(/src/**) deny is overridden only by the two explicit allow paths. Paths use gitignore semantics: a leading / is the project root, so Edit(/src/**) means <project>/src/**. (A bare /Users/... is NOT absolute; absolute paths need a double slash, Edit(//tmp/scratch.txt).) Verify the rule list any time with /permissions, which shows every rule and the settings file it came from.
If you’d rather block edits everywhere by default and open only what you list, run the session in dontAsk mode (auto-denies tools unless pre-approved via permissions.allow):
claude --permission-mode dontAsk
For a permanent guard on files that should never be touched casually (lockfiles, generated code, CI config), keep a deny rule in the checked-in .claude/settings.json:
{
"permissions": {
"deny": [
"Edit(/**/*.lock)",
"Edit(/dist/**)",
"Edit(/.github/workflows/**)"
]
}
}
Step 2: Hard editable-file list in the prompt
The deny rule stops writes; the prompt list keeps Claude from trying (and from filling the transcript with blocked-edit retries).
Editable files (DO NOT touch anything else):
- src/services/user.ts
- src/services/user.test.ts
For any other file:
- If the change requires touching it, STOP and ask before editing.
- Do NOT make consistency / formatting / cleanup changes anywhere.
The “stop and ask” line lets Claude flag a genuine cascade without unilaterally expanding.
Step 3: Redirect adjacent issues to FOLLOWUPS.md
If you spot issues in files outside the editable list:
- Do NOT fix them.
- Add a line to `FOLLOWUPS.md` describing the issue and its file:line.
- These become future tasks, not this PR.
Now Claude can be “helpful” without expanding scope. Every helpful spot becomes a documented future task. Pair this with a CLAUDE.md audit:
grep -in "improve\|clean up\|while you're there\|opportunistically" \
CLAUDE.md src/**/CLAUDE.md prompts/*.md
Replace each loose authorization with a tighter rule:
- Do NOT make changes outside the immediate task scope.
- Adjacent issues go in FOLLOWUPS.md, not into the current PR.
Step 4: Plan first, so scope is visible before any edit
For anything that might cascade, start in plan mode: Claude reads files and runs read-only commands but cannot edit source until you approve a plan. Press Shift+Tab to cycle the permission mode (it steps through default, accept-edits, then plan; the footer shows the current mode), or launch with claude --permission-mode plan. You then see the file list it intends to change and can shrink it before a single write happens. Approve the plan to execute, or keep refining.
One known wrinkle as of June 2026: cycling out of plan mode with Shift+Tab without approving a plan can inject a “work without stopping for clarifying questions” reminder (anthropics/claude-code#59203). If you want to abandon a plan, clear it explicitly rather than tab-cycling past it.
Step 5: For a received over-broad diff, revert the out-of-scope files
Don’t merge as-is, don’t reject entirely. Revert just the out-of-scope changes.
Whole-file revert when an entire file shouldn’t have been touched:
git diff --stat HEAD # see every changed file
git checkout HEAD -- <out-of-scope-files> # restore them to HEAD
Hunk-level revert when a single file has both wanted and unwanted changes:
git checkout -p src/services/user.ts
# answer "y" to discard each out-of-scope hunk, "n" to keep the real change
Then confirm the core change still passes:
pnpm test
If the over-broad edit happened in the current uncommitted session, /rewind (Claude Code’s checkpoint-based undo, available in v2) rolls code and conversation back to an earlier point in one command. Prefer it over git reset --hard, which is destructive and discards uncommitted work you may want.
Step 6: For genuine multi-file refactors, split into a PR chain
If the rename truly requires updating 8 callers, ship it staged:
PR 1: Add `findUser` alongside `getUser` (no caller changes; both exist).
PR 2: Migrate callers in `src/services/billing/` (3 callers).
PR 3: Migrate callers in `src/api/` (5 callers).
PR 4: Remove `getUser`.
Each PR is reviewable in 5 minutes. The total diff is the same; reviewability is 10x.
Step 7: Verify the tests weren’t weakened
For each test file in the diff:
git diff -- src/services/user.test.ts
# Flag removed expect/assert lines, added .skip, broadened type checks
git diff -- src/services/user.test.ts | grep -E '^-.*expect|^-.*assert|^\+.*skip'
If assertions got removed or tests skipped, the “test fix” was a regression hidden as a fix. Restore the original tests and re-prompt to fix the production code instead.
How to confirm it’s fixed
git diff --stat HEADlists only the files you intended (plus their tests)./permissionsshows theEditdeny rule in effect, and re-running the task produces a blocked-edit message for any out-of-scope file instead of a silent write.- The grep over test diffs returns nothing (no removed assertions, no new
.skip). FOLLOWUPS.mdhas grown with the adjacent issues Claude noticed but did not touch.
Prevention
- Keep a checked-in
Editdeny rule for files that should never be casually edited (lockfiles, generated code, CI config); enforcement beats good intentions. - Give every code prompt an explicit editable-file list with a “stop and ask” rule for anything outside it.
- Run cascade-prone tasks in plan mode so the intended file set is visible before any write.
- Maintain a
FOLLOWUPS.mdso Claude can surface adjacent issues without acting on them. - Audit
CLAUDE.mdfor “improve while you’re there” authorizations and remove them. - Split genuine multi-file refactors into a PR chain instead of one mega-PR; expect ~20-40 files as the reliable per-session ceiling.
- Spot-check test diffs: assertion removal or skip additions are creep disguised as fixes.
- Review against the original scope, not the final diff. The diff may look reasonable on its own.
FAQ
Why does Claude Code ignore “only edit billing.ts” in my prompt?
Because a prompt instruction is not a boundary. The model treats it as guidance and will cross it when it judges a related file “needs” the change. Only a permissions.deny rule (Step 1) is enforced by the harness. Put the rule in .claude/settings.local.json and the prompt instruction becomes a hint on top of a hard wall.
deny vs. plan mode: which should I use to limit scope?
Use both, for different jobs. Plan mode prevents all edits until you approve a plan, which is ideal for exploring scope first. A deny rule allows edits but blocks specific paths, which is ideal once you know exactly which files are in bounds. Plan mode for discovery; deny rules for execution.
How do I let Claude edit one file but block the rest of src/?
Combine a broad deny with narrow allows, remembering deny is evaluated first: "deny": ["Edit(/src/**)"] plus "allow": ["Edit(/src/services/user.ts)"]. Paths are relative to the project root because of the single leading slash.
Claude already made a 12-file mess. What’s the safest way to keep only the part I wanted?
Don’t git reset --hard. Use git checkout HEAD -- <file> to revert whole out-of-scope files and git checkout -p <file> to discard individual hunks inside a mixed file. If the changes are still uncommitted in the current session, /rewind undoes both code and conversation to a checkpoint.
Is there a file-count limit where refactors get unreliable? There’s no hard cap, but Anthropic’s guidance treats roughly 20-40 files per session as the reliable range; beyond that, accuracy drops and you should split the work into phases with a commit between each.