AI Rewrote Critical Logic You Did Not Ask It to Touch

Asked for a one-line rename and got a retry loop, a timeout, and a forEach silently rewritten too. Here is how to scope an AI edit to a surgical, auditable diff.

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.

SymptomMost likely causeGo to
Whole file came back when you asked for one renameWhole file pasted, no edit zone marked + no diff requestedStep 1, Step 3
Changes in files you never namedAgent has wide write permissionStep 4, FAQ on scoping
Logic changed but summary says “renamed only”You trusted the prose summaryStep 5
Constant / timeout / default value flippedOpen verb (“refactor”, “clean up”)Step 1, Step 2
Formatting churn on lines you did not touchNo “do not reformat” constraintStep 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 diff cleanly 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 Edit call, 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 .cursorignore file to block agent access to paths it should never touch. Set the agent’s run behavior under Settings > 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 in settings.json: deny rules win over ask and allow, so a deny on a directory blocks edits there even in permissive modes. ask rules like Bash(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_patch action over a chat rewrite. It requires an old_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 diff shows 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.jsondeny 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 — never refactor, improve, or polish without 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_patch mode for surgical edits.
  • Commit before every AI run so git diff and git restore stay clean.
  • Review the diff every time. Never trust the self-summary.

External references: Cursor Agent security docs, Claude Code settings & permissions, git restore documentation.

Tags: #Troubleshooting #Prompt #Prompt quality #Unwanted rewrite