| name | resolve-conflicts |
| description | Resolve merge conflicts. |
| user-invocable | true |
| allowed-tools | ["Bash","Read","Edit","Write"] |
resolve-conflicts — consolidate the best of both branches
A merge conflict means both branches edited the same region — and each side
changed it for a reason. The right resolution almost always keeps the intent
of both sides. The default failure mode is to grab one side wholesale
(--ours / --theirs) and silently throw the other branch's work on the
floor. Don't. Consolidate, don't choose.
When this fires
- Any git operation stops on a conflict:
git merge, git rebase,
git cherry-pick, git stash pop, git pull (merge or rebase),
git revert. The tell is
CONFLICT (content): Merge conflict in … and Unmerged paths: in
git status.
- "resolve the conflicts", "fix the merge conflicts", "I have a merge
conflict", "consolidate the best of both branches", "help me merge these
branches", "the branch won't merge cleanly".
- Delegated from another skill:
sync-pr-branch step 5 ("resolve any
conflicts fully"), clean-branches' rebase-onto-main step, or a stacked
gii MR. When one of those stops on a conflict, run this.
Verify before you act — don't trust mergeable alone
Before claiming and fixing a PR flagged by a cascade conflict scan
(post-merge step 1.5, ardi's opportunistic sweeps, wrap-up step 1), get
ground truth locally instead of acting on the API field directly:
git fetch origin main <branch> -q
git merge-tree --write-tree origin/main origin/<branch>
echo "exit: $?"
Two failure modes this catches:
mergeable == "UNKNOWN" is not "no conflict." GitHub returns UNKNOWN
right after a push or a sibling PR's merge, while it's still computing —
sometimes for minutes. A cascade scan that filters only
mergeable == "CONFLICTING" silently skips a PR stuck in UNKNOWN even
when it's genuinely conflicting. Treat UNKNOWN the same as
CONFLICTING: a candidate to verify, not a PR to skip.
mergeable == "CONFLICTING" (or a dirty mergeable_state) can itself be
stale. GitHub computes it against whatever main snapshot it last saw —
if a sibling PR merged since, the flag may still say conflicting even
though a fresh merge against current main is clean. Acting on a stale
flag risks wasted resolution effort. (Seen on ai-config#372: flagged
dirty, but git merge-tree against current main came back clean —
no manual resolution was needed, just a git merge + push.)
If git merge-tree exits clean, there's nothing to resolve — skip the PR (or,
if you want the branch to reflect the now-current main, do a plain
git merge origin/main && git push, no conflict markers involved). Only
proceed to the consolidation procedure below when it reports a real conflict.
Check git --version before trusting the exit code — on git <2.38 this
command fails a different way that a naive check misreads. --write-tree
mode doesn't exist before 2.38; on an older install the command errors with
fatal: unknown rev --write-tree (a non-zero exit, same as a real conflict)
or, if scripted with a text-based check instead of the exit code, an error
message that a naive grep -qi conflict fails to match — silently
misclassifying every single PR in a batch scan as clean (a false negative
that defeats the whole scan; hit across a 5-PR cascade scan on ai-config,
2026-07-03). Confirm git --version is 2.38+ before relying on this
command's exit code at all. On an older git, use the legacy 3-arg form
instead (same read-only guarantee, no worktree/checkout):
base=$(git merge-base origin/main origin/<branch>)
git merge-tree "$base" origin/main origin/<branch>
Core principle: consolidate the best of both branches
Each conflict hunk has three inputs — the common base, our side, and
their side. Picking a winner is right only when one side's change is
genuinely the same edit, or strictly subsumes the other. Otherwise the merge
should contain a synthesis that honors both:
- Two different features touching neighbouring lines → keep both edits.
- A refactor on one side + a bugfix on the other → apply the bugfix to the
refactored code.
- A rename on one side + an edit to the old name on the other → carry the edit
onto the new name.
CONFLICT (add/add) on the same newly-added file (common with sibling PRs)
→ compare :2: vs :3: first; keep the side that carries the newer
baseline (often main after a sibling PR merged), then manually re-apply any
newer deltas from the other side (review fixes, wording corrections, version
pins) before staging.
If you can't see why a side changed the hunk, you're not ready to resolve it —
go read the history first (step 2).
Procedure
-
Survey the battlefield. Know which operation you're in (the semantics of
"ours"/"theirs" depend on it — see the warning below) and list the
conflicted files:
git status
git diff --name-only --diff-filter=U
-
Understand BOTH sides before editing a single hunk. For each conflicted
file, look at the three-way picture and why each side diverged:
git log --merge --oneline -- <file>
git show :1:<file>
git show :2:<file>
git show :3:<file>
The :1:/:2:/:3: index stages work in any conflicted operation.
git log --merge is merge-only; for the others, inspect the incoming side
directly via the operation's ref — git show MERGE_HEAD (merge),
REBASE_HEAD (rebase), CHERRY_PICK_HEAD (cherry-pick), REVERT_HEAD
(revert), or the kept stash@{0} entry for a stash pop
(git stash show -p stash@{0}).
Ask: what problem was each side solving? Different features? One refactor +
one fix? A move/rename vs an in-place edit? The answer dictates the merge.
-
Consolidate. Edit the working file into a version that keeps the valuable
change from both sides, then delete every conflict marker
(<<<<<<<, =======, >>>>>>>). Take one side wholesale only when you've
confirmed the other side has nothing worth keeping — and note that you did.
Also check whether the merged result duplicates content that already
exists elsewhere in the file — a separate problem from the textual
conflict itself. This can arise two ways: a single incoming PR may
internally copy a sentence into a new section (the merge is clean, but
the result now says the same thing twice), or both branches may
independently add equivalent sentences in different sections (a
cross-branch variant with the same symptom). In both cases git merge/ reports zero conflicts even though the result
contains duplication — sometimes with a pronoun or reference that only
made sense in its original location. Grep the merged file for the
distinctive phrase from each side's addition before finishing;
if it already appears elsewhere, keep one copy (usually the version with
the clearer standalone referent) and drop the redundant one. (ai-config#446:
the merge itself was clean, but the incoming PR's own diff had separately
duplicated a CLAUDE.md sentence verbatim into a different section, where
"this policy" lost its antecedent — the intra-PR variant, caught by the
review bot as a distinct finding from the conflict resolution.)
⚠️ "ours" and "theirs" flip between merge and rebase
- In a merge (
git merge X on branch B): ours/:2: = B (where you
are), theirs/:3: = X (being merged in).
- In a rebase (
git rebase X on branch B): rebase replays your commits
onto X, so ours/:2: = X, the branch you're landing on, and
theirs/:3: = your own commit being replayed. The opposite of a merge.
Confirm which physical branch is "ours" before you reach for --ours /
--theirs — getting this backwards silently keeps the wrong side. (This is
why the skill defaults to consolidating both rather than picking a side.)
Relationship to other skills
sync-pr-branch (aliases sync / merge-main / resync-branch) — its
step 5 says "resolve any conflicts fully"; this is that step's how-to.
Run it when that sync stops on a conflict, then return to finish the sync.
clean-branches — when rebasing a stale branch onto main hits a
non-trivial conflict, resolve it here instead of skipping the branch.
gii / split-concerns — stacked or parallel MRs are the usual source
of conflicts; this is how you clear them once they collide.
check-history — run before implementing to avoid creating avoidable
conflicts/regressions in the first place; this skill cleans up the ones that
still happen.
deconflict-sessions / session-lock — not this skill. Those are
about two AI sessions clobbering one local checkout (a locking concern),
not git content conflicts. Different sense of "conflict."
Anti-patterns
- ❌ Blind
git checkout --ours/--theirs <file> (or git merge -X ours/theirs)
without confirming the dropped side had nothing worth keeping — the classic
silent-data-loss move.
- ❌ Resolving a hunk before understanding why each side changed it.
- ❌ Mixing up "ours"/"theirs" in a rebase (they're reversed vs a merge).
- ❌ Committing with conflict markers still in a file — always
git diff --check.
- ❌ Committing the resolution without running the repo's build/lint/test checks.
- ❌
git rebase --skip / git merge --abort to dodge a conflict you were
asked to resolve.
- ❌ Force-pushing a half-resolved merge/rebase.