Merge Conflict After AI Edits: Resolution Guide

Your branch plus AI edits conflict with main. Resolve it without losing the AI's real improvements, and stop mistaking format noise for logic conflicts.

You let Claude Code, Cursor, or Codex run for half an hour, six files of edits land on disk, and the moment you git pull --rebase origin main the terminal fills with CONFLICT (content): Merge conflict in src/.... The painful part isn’t the conflict itself. It’s that the agent re-ordered imports, normalized whitespace, and changed real logic in the same files, so you can’t tell which hunks are noise and which are semantic conflicts.

TL;DR (fastest fix)

  1. Commit the AI’s work so it’s a recoverable object, then make a backup branch.
  2. Re-run the rebase letting git auto-resolve the format-only hunks: git pull --rebase -X theirs origin main keeps the AI’s version on conflicting hunks while still pulling in main’s non-conflicting commits.
  3. For the handful of files left in conflict, three-way diff them and hand-edit only the real logic.
  4. Run tests/lint/build, then git rebase --continue.

Critical gotcha: during a rebase, ours and theirs are swapped relative to a normal merge. Your replayed commits (the AI edits) are theirs; the branch you’re landing onto (main) is ours. The rest of this guide uses the rebase meaning. If you got here via git merge instead, flip every ours/theirs below.

Common causes

Ordered by how often each pattern shows up in agent-driven merge conflicts.

1. AI edited files that main also moved

The most common case. You forked an hour ago; in that window a teammate pushed three commits touching the same src/server/auth.ts. The agent had no idea remote moved and rewrote the file from your local state.

CONFLICT (content): Merge conflict in src/server/auth.ts
Auto-merging src/server/auth.ts

How to spot it: git log origin/main --since="2 hours ago" -- <file> shows commits on the same file from someone else.

2. Imports auto-organized into a different order

Cursor and Copilot often run eslint --fix or Prettier on save and reorder imports. If the agent’s grouping differs from the team’s config, the entire import block gets rewritten and you get a giant-looking conflict that’s pure ordering noise.

<<<<<<< HEAD
import { z } from "zod";
import { db } from "@/lib/db";
import type { User } from "@/types";
=======
import type { User } from "@/types";
import { db } from "@/lib/db";
import { z } from "zod";
>>>>>>> ai-branch

How to spot it: both sides of the hunk contain the same set of imports, only the order or blank lines differ.

3. Whitespace and line endings rewritten

Aider and Windsurf often have the model regenerate whole functions. The model sometimes flips tabs to spaces, CRLF to LF, or restyles semicolons. git diff looks like the whole file changed; git diff -w is almost empty.

How to spot it: git diff --stat shows large insertions/deletions but git diff -w --stat drops to single digits.

4. AI moved code across files; main moved the same code elsewhere

The agent decided to move formatDate from utils/format.ts into lib/date.ts. At the same time main moved the same function to shared/format.ts. The rebase can’t reconcile two deletes plus two adds and raises an add/add conflict.

CONFLICT (add/add): Merge conflict in lib/date.ts

How to spot it: git status shows both both added and deleted by us / deleted by them entries.

5. AI fixed a bug; main fixed the same bug differently

Both sides are correct, but the implementations diverge. You asked the agent to fix a race condition with a mutex; a teammate landed an atomic-based fix. Tests pass either way, but the code conflicts.

How to spot it: each side of the conflict passes the test suite in isolation. The implementations are simply different.

6. Lockfile or generated-file conflicts

package-lock.json, pnpm-lock.yaml, migrations/*.sql, schema.prisma. These always conflict in hundreds or thousands of lines. There’s a dedicated guide: AI lockfile conflicts.

How to spot it: the conflicting path is a lockfile, migration, or anything under a generated/ directory.

Shortest path to fix

In order. Every step is reversible. Worst case, git rebase --abort returns you to Step 1.

Step 1: Freeze the AI edits into a single commit

If the agent’s changes are still in your working tree, commit them so they become a comparable, resettable object.

git add -A
git commit -m "WIP: AI edits before rebase"
git rev-parse HEAD  # save this SHA; you'll cherry-pick or diff against it

Then create a safety branch:

git branch backup/ai-edits-$(date +%Y%m%d-%H%M)

If the rebase goes sideways, git checkout backup/... brings you back in one command.

Step 2: Turn on rerere so you only resolve each conflict once

If the agent re-runs (very common: you abort, re-prompt, and it produces the same edit), you’ll hit the same conflict again. Enable git rerere so git records your resolution and replays it automatically next time:

git config --global rerere.enabled true

Git invokes rerere automatically during both merge and rebase. Turn it on before the messy rebase, not after.

Step 3: Let git absorb the format-only conflicts with -X

Many “conflicts” are import ordering, whitespace, or line endings. Bias git toward one side at the hunk level before resolving. Remember the rebase swap from the TL;DR.

# Keep the AI's version on conflicting hunks; still pull main's other commits.
# During a rebase, your replayed AI commits are "theirs".
git pull --rebase -X theirs origin main

# Or the opposite: prefer main's version on conflicting hunks.
# During a rebase, the branch you land onto (main) is "ours".
git pull --rebase -X ours origin main

-X theirs / -X ours only choose at the hunk level. They do not drop net-new code that exists on only one side. In practice this clears most of the format-only conflicts in one pass, leaving only a handful of real ones to resolve by hand.

To absorb whitespace-only differences (the ort default strategy supports this option):

git rebase -X ignore-all-space origin/main

Step 4: Bucket the remaining conflicts by type

git status --short | grep "^UU"  # files still in conflict (both modified)

Handle each bucket differently. These git checkout commands assume you’re mid-rebase, so the side labels follow the rebase meaning:

TypeResolution
Pure import / formattingKeep the AI version: git checkout --theirs <file>, then run your local formatter
Lockfile (regenerate from main)Take main’s version: git checkout --ours <file>, then npm install to regenerate
Real logic conflictHand-edit using the three-way diff in Step 5

If you’re unsure whether you’re rebasing or merging, check: git status says either interactive rebase in progress / rebase in progress, or You have unmerged paths after a git merge. The ours/theirs meaning flips between the two.

Step 5: Three-way diff the real logic conflicts

For files with semantic conflicts, open a three-way view:

git mergetool --tool=vimdiff  # or vscode / meld

Or pull each stage manually. Stage :1 is the common ancestor, :2 is ours, :3 is theirs. During a rebase that means :2 is main and :3 is your AI commit:

git show :1:src/server/auth.ts > /tmp/base.ts    # common ancestor
git show :2:src/server/auth.ts > /tmp/ours.ts    # main side (during rebase)
git show :3:src/server/auth.ts > /tmp/theirs.ts  # AI side (during rebase)
diff -u /tmp/base.ts /tmp/ours.ts    # what main actually changed
diff -u /tmp/base.ts /tmp/theirs.ts  # what AI actually changed

Only after you see “what main changed” and “what AI changed” as two independent diffs can you decide how to merge, instead of blindly picking a side.

Step 6: Run the full check before continuing the rebase

git add <resolved files>
npm test && npm run lint && npm run build
git rebase --continue

If you want out at any point:

git rebase --abort  # back to the commit from Step 1

How to confirm it’s fixed

  • git status reports no unmerged paths and nothing to commit, working tree clean.
  • git rebase --continue finishes without re-prompting (or there was no rebase in progress to begin with).
  • git diff origin/main...HEAD --stat shows only the files you intended to change, with no surprise lockfile or whitespace-only entries.
  • Tests, lint, and build all pass on the final tree, not just on each side in isolation.

Prevention

  • Run git pull --rebase origin main before letting the agent start, to shrink the divergence window.
  • Put rules in CLAUDE.md / .cursorrules / AGENTS.md: “read the file first, don’t reorder imports, don’t reformat whitespace.”
  • Keep AI commits small and frequent (one task per commit), so a conflict costs at most one commit of work.
  • Add a repo-root .editorconfig plus a pre-commit formatter hook so AI output matches team style automatically.
  • For lockfiles and generated files, add a .gitattributes rule so they don’t need manual diffing. The built-in union driver (package-lock.json merge=union) keeps both sides’ lines, which you then regenerate with npm install. A bare merge=ours only works if you first define it: git config merge.ours.driver true.
  • Turn on git config --global rerere.enabled true once, globally, so repeat conflicts resolve themselves.
  • For large refactors, have the agent write a plan first; approve the file list before letting it touch anything.

FAQ

Why did git checkout --theirs pull in the wrong version? Because you’re in a rebase, not a merge. During a rebase the labels are swapped: theirs is your replayed commits (the AI edits) and ours is the branch you’re landing onto (main). If you wanted main’s version mid-rebase, use git checkout --ours.

Does -X theirs throw away the AI’s new code? No. -X theirs (and -X ours) only decide which side wins on conflicting hunks. Code that exists on only one side, with no overlap, is kept either way. It’s a tiebreaker, not a delete.

The same conflict keeps coming back every time the agent re-runs. How do I stop re-resolving it? Enable git rerere (git config --global rerere.enabled true) before the rebase. Git records your manual resolution and replays it automatically the next time the identical conflict appears.

My whole file shows as conflicting but it’s only whitespace. Fastest fix? Confirm with git diff -w --stat (if it drops to near-zero lines, it’s pure whitespace), then re-run the rebase with git rebase -X ignore-all-space origin/main so git stops treating spacing differences as conflicts.

How do I tell a real logic conflict from format noise without reading every line? Compare git diff --stat against git diff -w --stat. If the second is tiny, it’s whitespace. For imports, check whether both sides of the hunk hold the same identifiers in a different order. Anything left after those two checks is a genuine semantic conflict worth a three-way diff.

Can I bail out without losing the AI’s work? Yes. git rebase --abort returns you to the commit from Step 1, and your backup/ai-edits-* branch is an independent copy regardless.

Tags: #AI coding #Debug #Troubleshooting