AI Removed Working Logic During Refactor: Find It and Restore It

An AI agent "simplified" your code and quietly deleted a branch real users depend on. Bisect to the commit, cross-check prod logs, restore just the deleted hunks, and add a regression test.

You asked Claude Code or Cursor to “refactor this handler — make it cleaner.” The diff looked great: 50 lines down to 28. You merged. Two days later support tickets show “old users hit the wrong page after login” or “Safari uploads fail.” The agent decided a branch “looked unused” and removed it — but that branch was the fallback for a real customer scenario.

Fastest fix: find the AI commit with git bisect, then restore only the deleted hunks with git restore --source=<good-commit> -p <file> (you don’t have to revert the whole refactor). Then add a named regression test so the branch can’t be deleted silently again. The rest of this guide shows how to confirm which branches are actually live in production before you decide what to keep.

This is not a rare event. Agentic editors are aggressive about “tidying,” and there were widely reported cases in early 2026 (including a Cursor 3 silent-revert/delete bug) where logic vanished from a diff without the user noticing. Treat any large agent refactor as untrusted until you’ve verified the live code paths.

Common causes

Ordered by hit rate.

1. AI treats “no tests” as “dead code”

The most common mode. The agent sees if (user.legacyRole === "admin") with no covering test and removes it. But legacy admins are 2% of prod traffic — the day you ship, somebody gets 401’d.

- if (user.legacyRole === "admin" || user.role === "admin") {
-   return grantAdminAccess(user);
- }
+ if (user.role === "admin") {
+   return grantAdminAccess(user);
+ }

How to spot it: The removed branch references legacy, old, v1, fallback, deprecated, or an inactive-but-still-present field.

2. Edge cases weren’t in the prompt

You said “simplify this parser.” You didn’t say “must preserve handling for empty strings, zero-width Unicode, CRLF.” The agent stripped what it saw as “overly defensive” null checks and normalization.

- if (!input || input.trim() === "") return defaultValue;
- input = input.normalize("NFC").replace(/​/g, "");
  return parse(input);

How to spot it: Prod throws TypeError: Cannot read properties of undefined or encoding errors, with stack traces pointing into the refactored function.

3. Refactor scope was vague

“Clean up this file” or “modernize this code” lets the agent freelance. It removes retry, timeout, or circuit-breaker logic that “looked like a hack” — because the comment explaining why it was added wasn’t there.

How to spot it: The deleted code has no surrounding explanatory comments, but git blame shows it was added in a post-incident commit.

4. AI mistook a feature flag for a deprecated branch

- if (isFeatureEnabled("new-checkout", user)) {
-   return newCheckoutFlow(user);
- }
- return oldCheckoutFlow(user);
+ return newCheckoutFlow(user);

The agent assumed the flag was fully rolled out and removed the other side. The flag was still at 50% — half your users suddenly land in an under-tested path.

How to spot it: The deleted code includes isFeatureEnabled / getFlag / LaunchDarkly-style switches.

5. AI simplified error handling and removed an intentional swallow

Some try { ... } catch (e) { /* intentionally swallowed */ } blocks are deliberate (analytics shouldn’t block checkout). The agent sees the empty catch and “fixes” it to throw or logger.error — and now checkout fails when analytics 500s.

How to spot it: A spike of new ERROR-level log entries that didn’t exist before, plus business operations starting to break.

6. AI merged “duplicate” overloads with different signatures

The agent combined getUserById(id: string) and getUserById(id: number) into one, dropping the string-ID fallback parse. Old URLs still send string IDs and immediately 404.

How to spot it: TypeScript overload errors or runtime id is undefined.

Shortest path to fix

Step 1: Run the full test suite and see what breaks

npm test -- --reporter=verbose 2>&1 | tee /tmp/test.log

Don’t trust tests alone — if the deleted branch had no coverage, tests will pass while users break. Also run:

# E2E / integration
npm run test:e2e

# Walk the key business scenarios manually from a checklist

Step 2: Cross-reference prod logs for branches that actually run

Open the last 7 days of prod logs and check the entry points of the changed file:

# e.g. distribution of user agents / roles hitting the checkout handler
grep "POST /api/checkout" /var/log/app.log | awk '{print $7}' | sort | uniq -c

Output reveals “what code paths are alive in prod” — e.g. 8% of traffic is IE/Safari, and the agent removed the branch handling them.

Step 3: git bisect to find the regression commit

If multiple commits landed between known-good and known-bad, bisect is fastest. It does a binary search, so even a 1000-commit range converges in roughly 10 checkouts.

git bisect start
git bisect bad HEAD                    # currently broken
git bisect good v1.2.3                 # last known-good tag
# automated: run a command that exits 0 = good, non-zero = bad
git bisect run npm test -- failing-spec.test.ts
git bisect reset                       # when done, return to HEAD

The contract for git bisect run is exit-code based: your script must exit 0 for a good commit, any code 1-124 (or 126/127) for a bad one, and 125 to tell bisect to skip an untestable commit (e.g. a build that won’t compile). If no test reproduces the bug (the deleted branch had no coverage — the most common case here), bisect manually instead: run git bisect good or git bisect bad each round after checking the live behavior by hand.

In a few rounds you’ll have the exact AI commit that introduced the regression. See the official git-bisect docs for the full state machine.

Step 4: Diff and restore the removed logic

git show <bad-commit> -- src/checkout/handler.ts

For each deleted block, decide:

Removed codeAction
Truly deadKeep deleted; commit message explains why
Still used but outdatedModernize the syntax, preserve the behavior
Handled an edge caseRestore that hunk as-is
Feature flag branchCheck flag rollout; restore if not at 100%

Restore the whole file, or just the hunks you want:

# whole file back to the good version
git restore --source=<good-commit> src/checkout/handler.ts

# hunk-by-hunk (interactive: y to keep a deleted block, n to skip)
git restore --source=<good-commit> -p src/checkout/handler.ts

git restore --source=<commit> -p <file> is the current, semantically-correct form (Git 2.23+); the older git checkout -p <good-commit> -- <file> still works identically if you’re on an older Git. In the interactive prompt, press s to split a large hunk into smaller pieces so you can restore only the deleted branch and leave the AI’s legitimate cleanup in place. See the git-restore docs for the patch-mode keys.

Step 5: Add tests before committing

Logic the AI deleted once will be deleted again. Add a regression test immediately:

// Example: legacy admin must still log in
test("legacy admin role still grants access (regression: removed in refactor 2026-05-22)", () => {
  const user = { id: 1, legacyRole: "admin" };
  expect(authorize(user)).toBe(true);
});

The test name should encode why — not just “test legacy admin,” but “legacy admin role still grants access (regression: removed in refactor 2026-05-22).”

How to confirm it’s fixed

Don’t rely on “tests are green” alone — the bug existed because the branch had no test. Confirm all three:

  1. The new regression test fails on the broken commit, passes on the fix. Quick check: git stash your restore, run the new test (it should fail), git stash pop, run again (it should pass). If it passes both ways, the test isn’t actually exercising the restored branch.
  2. The real user path works end-to-end. Reproduce the original ticket manually — log in as the legacy-admin role, upload from Safari, hit the old string-ID URL — not just the unit test.
  3. The error/log signal is gone. If Step 5’s cause was a log spike (intentional swallow turned into a throw, or TypeErrors in the refactored function), watch the same query for 15-30 minutes after deploy and confirm the rate drops back to baseline.

Prevention

  • Only refactor code that has test coverage — if there are no tests, write them before refactoring
  • List preserved behaviors explicitly in the prompt: "keep handling for empty strings, IE11, legacy admin"
  • Comment important edge-case handling with // IMPORTANT: handles X case from incident #1234 — agents respect explained code
  • Add to CLAUDE.md / AGENTS.md: “do not remove branches that have comments, reference feature flags, or contain ‘legacy’ / ‘fallback’ keywords, unless explicitly told to”
  • Large refactors must go through PR; any diff with > 30 deleted lines requires a second reviewer
  • Use the AI pre-commit review workflow — have the agent diff its own output and justify each deletion

FAQ

How do I find the exact commit where the AI deleted the code if I don’t have a failing test? Bisect manually. Run git bisect start, mark git bisect bad HEAD and git bisect good <last-good-tag>, then at each round reproduce the real symptom by hand and type good or bad. Without a script you can’t use git bisect run, but the binary search still saves you from reading every diff. Once you land on the commit, git show <commit> -- <file> shows exactly what was removed.

Can I restore one deleted block without undoing the AI’s good changes? Yes. Use git restore --source=<good-commit> -p <file> and answer the per-hunk prompt — y to bring back a deleted block, n to keep the AI’s version, s to split a hunk that mixes both. This keeps legitimate cleanup and only resurrects the branch you need.

The tests all pass but production still breaks. Why? The deleted branch had no test coverage, which is why the agent thought it was dead code. Green tests prove nothing about an untested path. Cross-reference production logs (Step 2) to see which code paths real traffic actually hits, then add a regression test for that path before you trust the suite again.

How do I stop the agent from deleting the same logic next time? Three layers: (1) add a named regression test, (2) leave a // IMPORTANT: handles X from incident #1234 comment on the branch — agents are far more conservative around explained code, and (3) add a rule to CLAUDE.md / AGENTS.md forbidding removal of branches that carry comments, reference feature flags, or contain legacy/fallback keywords unless explicitly asked.

Should I just revert the whole AI commit instead? Only if the refactor was net-negative. If the cleanup was mostly good and one branch was wrongly dropped, a full git revert throws away the good work too. Prefer hunk-level restore plus a regression test. A full revert is the right call when you’re mid-incident and need the bleeding to stop immediately — restore correctness first, redo the cleanup carefully later.

Tags: #AI coding #Debug #Troubleshooting