You asked the model to rename a parameter in one function. It did. It also “cleaned up” a retry loop two functions below, swapped a forEach for a map, and silently changed a default timeout from 30s to 10s. None of that was in the prompt. None of it appears in the model’s summary (“renamed parameter as requested”). Worse, the tests still pass because the retry path runs only in production. You discover the change in a 3am incident, not in code review.
Fastest fix: stop asking for a “new file” and force a constrained edit. Add three lines to your prompt: name the one function to change, add Do not modify any other function or any imports, and require a unified diff back. Then run git diff HEAD and audit every hunk yourself. If your tool has an exact-match Edit/apply action (Cursor, Claude Code, Codex), use that instead of a chat rewrite. The model literally cannot edit a region whose old_string does not match, so unmarked code stays untouched.
The root reason agents over-edit: most chat-style instructions get read as a license to “leave the code better than you found it” — a habit copied from senior engineers in the training data who do exactly that. Scope is something you have to impose; the default is “improve everything.”
Which bucket are you in?
Match your symptom to the cause and jump to the fix that applies.
| Symptom | Most likely cause | Go to |
|---|---|---|
| Whole file came back when you asked for one rename | Whole file pasted, no edit zone marked + no diff requested | Step 1, Step 3 |
| Changes in files you never named | Agent has wide write permission | Step 4, FAQ on scoping |
| Logic changed but summary says “renamed only” | You trusted the prose summary | Step 5 |
| Constant / timeout / default value flipped | Open verb (“refactor”, “clean up”) | Step 1, Step 2 |
| Formatting churn on lines you did not touch | No “do not reformat” constraint | Step 2 |
Common causes
1. The verb is open-ended
Refactor, improve, clean up, and modernize all give the model permission to touch anything that smells suboptimal — which, by training-set standards, is most pre-existing code.
How to spot it: your verb has no narrow object. “Refactor this module” means “improve everything you can reach.”
2. Whole file pasted, no edit zone marked
When the model receives the whole file and is told to “make a change,” regenerating the whole file is the path of least resistance. Anything regenerated drifts.
How to spot it: you pasted 400 lines, asked for one rename, and got 400 lines back.
3. The agent has wide write permission
In Cursor Agent, Claude Code with auto-accept, or Codex with broad scope, the model can touch any file in the workspace. Without explicit scoping, “scope” becomes “everything the agent can read.”
How to spot it: changes appear in files you never named.
4. No diff format was requested
If the output format is “the new file,” the model has to produce every line, and every line is a chance to drift. If the output format is “unified diff,” the model is forced to emit only changed hunks.
How to spot it: you got a full new file back, not a diff.
5. The summary says “renamed” but the diff says more
Models sometimes under-report in their summary. The summary is a self-report; you cannot trust it for an audit. Audit the diff itself.
How to spot it: the summary mentions one change; the diff shows several.
Before you re-prompt
- Commit the current state first so you can
git diffcleanly afterward. - Write down exactly which lines/functions should change and which must not.
- Save the over-edited output so you can compare it against the surgical version.
- For agent runs, narrow the scope before pressing go (see Step 4).
- Decide your output mode: a unified diff, an exact-match
Editcall, or a constrained full file.
Shortest path to fix
Step 1: Replace the open verb with a surgical operation
Bad: Refactor this module for readability.
Good: In function handlePayment, rename parameter amt to amountCents.
Update references inside handlePayment only.
Do not modify any other function. Do not touch imports.
Do not reformat unchanged lines.
A verb with a named object and a named scope removes the “improve everything” license.
Step 2: Declare a “do not modify” list
Constraints:
- Do not modify any function other than handlePayment.
- Do not touch imports, exports, or comments.
- Do not change constants MAX_RETRIES or TIMEOUT_MS.
- Do not reformat unchanged lines.
- If you would normally clean something up, list it under
"Suggested follow-ups" instead of doing it.
The follow-ups clause matters: it gives the model a place to put its “improvements” so it stops smuggling them into the diff.
Step 3: Demand a unified diff, not a file
Ask for output in patch form:
Return only a unified diff:
--- path/to/file.ts
+++ path/to/file.ts
@@ ... @@
- old line
+ new line
Then a section "Suggested follow-ups:" listing anything you noticed
but did not change.
Diff output forces minimal change, because regenerating untouched code is wasted work. A bonus: you can pipe the diff straight into git apply to review locally.
Step 4: Lock the scope at the tool level (strongest guarantee)
Prompt constraints are advice. Tool permissions are enforcement. Use whichever your tool offers:
- Cursor: add a
.cursorignorefile to block agent access to paths it should never touch. Set the agent’s run behavior underSettings > Cursor Settings > Agents > Run Mode; recent versions add an “Auto-review” mode that routes shell, MCP, and fetch calls through a classifier before they run. Note that Cursor describes this classifier as best-effort convenience, not a security boundary, so do not rely on it as a hard guard. The Agent Sandbox (generally available on all platforms as of early 2026) confines writes to the workspace. Put recurring scope rules in.cursor/rules/. - Claude Code: put scope limits in
CLAUDE.md(for example,Do not modify files in src/lib/ or src/utils/ unless explicitly asked). For hard guarantees use permission rules insettings.json:denyrules win overaskandallow, so adenyon a directory blocks edits there even in permissive modes.askrules likeBash(git checkout:*)force a confirmation prompt for risky commands. Claude Code also treats paths like.git/and.claude/as protected, historically prompting for them even under--dangerously-skip-permissions, though the exact behavior has shifted across versions, so verify against your installed build. - Cursor / Claude Code / Codex: prefer the exact-match
Edit/apply_patchaction over a chat rewrite. It requires anold_string(or anchor) that matches the source verbatim, so the model cannot edit unmarked regions — the match simply fails. This is the strongest mechanical guard against drift.
Step 5: Audit the diff, not the summary
Never accept the model’s prose summary as truth.
git diff HEAD
Scroll every hunk. To see only which files were touched in an agent session:
git diff --name-only HEAD
If a hunk surprises you, revert that hunk alone. On git 2.23+ use the modern command; the older one still works:
git restore -p path/to/file.ts # modern, recommended
git checkout -p path/to/file.ts # older, still valid
Both open an interactive picker so you keep the rename and drop the unwanted retry-loop change.
Step 6: Split multi-region edits into multiple prompts
If you genuinely need to change three functions, run three prompts. A combined prompt blurs scope, and the model averages constraints across regions instead of honoring each one.
How to confirm it is fixed
git diffshows changes only in the files and functions you named.- No imports, exports, or constants moved.
- Existing tests still pass with no test files changed by the model.
- The model’s “Suggested follow-ups” list is non-empty if it noticed other issues — meaning it respected the boundary instead of silently acting.
- A second run with the same prompt produces a similar diff (stable, scoped behavior).
FAQ
The agent edited a file I never mentioned. How do I stop that specifically?
Block it at the tool, not the prompt. In Cursor, add the path to .cursorignore and rely on the Agent Sandbox to confine writes to the workspace. In Claude Code, add a deny rule for that directory in settings.json — deny overrides ask and allow, so it holds even in auto-accept. Naming the path in your prompt alone is not reliable.
My prompt said “refactor.” Is this the model’s fault?
No. If you wrote “refactor,” “polish,” or “modernize,” the model obeyed a broad instruction. The fix is to change the verb to edit, rename, or replace with a named scope, not to blame the model.
Should I use a smaller model for surgical edits?
Often, yes. A mid-tier model (for example Sonnet 4.6, GPT-5.5 Instant, or Gemini 3.1 Pro) frequently follows tight constraints as well as or better than a top-tier reasoning model, which is more inclined to “help” by improving surrounding code. Test on your own repo before deciding.
How do I keep the unwanted change but undo the rest, or vice versa?
Use hunk-level revert: git restore -p <file> (or git checkout -p <file> on older git) opens an interactive prompt where you choose per hunk. This lets you keep the rename and discard the silent timeout change without redoing the whole edit.
Why do the tests pass even though logic changed?
Coverage gaps. The over-edited path (a retry loop, an error branch, a production-only timeout) is often the part your test suite never exercises. Passing tests are not proof the diff is safe; reading the diff is.
Can I make this safe by default for every session?
Yes. Commit before every AI run, default your prompt verbs to edit/rename/replace, require diff output for any change over ~20 lines, and never let an agent commit directly on high-stakes code — review the diff first.
Prevention
- Default prompt verbs:
edit,rename,replace— neverrefactor,improve, orpolishwithout an explicit scope. - Require diff output for any code edit over ~20 lines.
- Bracket the edit zone with comments —
// AI-EDIT-START/// AI-EDIT-END— and tell the model to modify only inside. - Use exact-match
Edit/apply_patchmode for surgical edits. - Commit before every AI run so
git diffandgit restorestay clean. - Review the diff every time. Never trust the self-summary.
Related reading
- AI over-edits when light rewrite was requested
- Unclear task boundary
- Too many tasks one prompt
- Model fills missing details
- Prompt lacks context hierarchy
External references: Cursor Agent security docs, Claude Code settings & permissions, git restore documentation.
Tags: #Troubleshooting #Prompt #Prompt quality #Unwanted rewrite