Resolve merge conflicts
Agent skills we use daily in the studio — reusable, on-demand context for Claude Code, Cursor & other skills.sh-compatible AI agents. Install: npx skills add stealth-engine/skills
npx -y skills add stealth-engine/skills --skill resolve-merge-conflictsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Resolve git merge/rebase conflicts non-destructively — preserving the intent of BOTH sides — and hand off to a human when a conflict can't be resolved safely. Use when a branch or PR has conflicts with its base ('this branch has conflicts that must be resolved', GitHub mergeable=CONFLICTING / mergeStateStatus=DIRTY), when a rebase / merge / cherry-pick / stash / revert stops with conflict markers (<<<<<<< ======= >>>>>>>), when a PR is behind base and needs updating, when syncing a long-lived branch or back-merging a hotfix, or when deciding rebase vs merge. Covers the safe-resolution rules (no blind -X ours/theirs, hunk-by-hunk with diff3, abort/reflog escape hatches, verify-after), the rebase ours/theirs inversion, lockfile/rename/semantic conflicts, and the escalate-when-stuck criteria.
SKILL.md
8.0 KB, as published. Nobody here has run it
Resolve merge conflicts (non-destructively)
Resolve git conflicts so that both sides' intent survives, verify the result, and stop and ask a human the moment a safe resolution isn't obvious. The git mechanics are the easy part; the danger is silently dropping someone's work — a blind "take theirs," a clean-looking textual merge that's logically broken, or a force-push over a teammate's commits.
Command recipes live in reference/playbook.md (load on
demand). This body is the decision rules.
First: rebase or merge?
You update a feature/PR branch against its base one of two ways. Either is fine — pick by context, not dogma:
git rebase origin/main | git merge origin/main | |
|---|---|---|
| History | linear (replays your commits) | adds a merge commit |
| Conflicts | may re-hit the same conflict per replayed commit (turn on rerere) | resolve once |
| Push after | --force-with-lease (rewrites your branch) | normal push |
| Best for | your own unshared PR branch | a branch others also commit to |
In a squash-merge repo (see
git-trunk-branch-and-pr-automation)
the feature branch's history is flattened at merge anyway, so a merge commit on the
branch is harmless — when a rebase keeps re-conflicting, merge instead and
resolve once. Never rebase a branch other people are actively committing to.
The non-negotiable safety rules
- Never blind-resolve a whole side.
-X ours/-X theirs(strategy options) andgit checkout --ours/--theirs <file>take one side wholesale and throw the other away silently. They're only correct for files you will regenerate (e.g. lockfiles) or a file that genuinely should be one side entirely — never as a "make the markers go away" shortcut. - Resolve hunk-by-hunk, preserving both intents. A conflict means both sides
changed the same region. The correct result is usually neither side verbatim
but a combination. Turn on
diff3/zdiff3so you can see the common ancestor and reason about what each side changed from (recipe in the playbook). - Know the rebase ours/theirs inversion. During
git merge,ours=your branch,theirs=incoming. Duringgit rebase, it's flipped:ours=the branch you're replaying onto (e.g.main),theirs=your commits. Acting on the wrong one discards exactly the work you meant to keep. - Keep the escape hatch ready.
git merge --abort/git rebase --abort/git cherry-pick --abortreturns you to a clean pre-conflict state. If unsure, abort and reassess rather than push a half-resolved tree.git reflog+ORIG_HEADrecover from a bad finish. - Force-push only
--force-with-lease, only your own PR branch. Plain--forceclobbers teammate commits pushed since your last fetch;--with-leaserefuses if the remote moved. - Never commit conflict markers. Run
git diff --checkandgit diff --cached --check(staged markers won't show in the unstaged diff aftergit add), plus a grep for<<<<<<<, before--continue/commit.
The loop
1. UNDERSTAND → what's conflicting + why (mergeable status, conflict list, diff3)
2. CLASSIFY → per file: content · rename/delete · lockfile/generated · binary · semantic
3. RESOLVE → hunk-by-hunk, both intents; regenerate (don't hand-merge) lockfiles
4. VERIFY → no markers; build + tests; diff sanity (no unrelated lines vanished)
5. CONTINUE → git add; --continue / commit; push (--force-with-lease if rebased)
│
└─ at ANY step, if a safe resolution isn't clear → ABORT or stop → HAND OFF
Conflict types — and how each is resolved
- Content (same lines changed) → the default: merge both intents by hand.
- Rename/delete & add/add → decide whether the file should exist and where; don't
let one side's deletion silently win. (
git statuslabels these.) - Lockfiles (
package-lock.json,pnpm-lock.yaml,yarn.lock,Cargo.lock) → do not hand-merge. Take one side, then regenerate from the merged manifest (npm install --package-lock-only/pnpm install --lockfile-only/cargo generate-lockfile/ etc.) so the lock matches its resolved manifest (package.json,Cargo.toml, …). This is the legitimate use of "take one side." - Generated/build output → regenerate from source, never merge by hand.
- Binary → you can't merge text; pick a side deliberately or escalate.
- Semantic → the file merges with zero markers but is logically broken (one side renamed a function the other side calls). Textual success ≠ correctness — only build + tests catch these.
Escalate to a human when…
Hand off (with context) rather than guess when:
- The conflict is a genuine design clash — both sides deliberately changed the same logic in incompatible ways; only the authors can say which intent wins.
- It's in code whose intent you can't determine, or in migrations / schema / data files where a wrong merge corrupts state.
- A lockfile regeneration pulls in unexpected dependency changes.
- The branch is from a fork / untrusted PR — you can't safely run its checked-out
code or push to it (consistent with the fork-safety rule in
autonomous-pr-driver). Validate via API; don't resolve locally. - A rebase keeps re-conflicting across many commits, or the conflict spans many files beyond what you can verify.
How to hand off: state what files conflict, what you tried, and why you stopped — then leave the branch in a clean (aborted) state, or push the partial resolution to a clearly-labelled branch and ask. Same posture as the PR driver's never-self-merge: when in doubt, a human decides.
Verify (before you call it done)
git diff --check→ no leftover conflict markers / whitespace errors.- Build + run the tests — the only thing that catches semantic conflicts.
- Diff sanity: compare against both parents; confirm no unrelated lines were dropped during the resolution (the silent-data-loss failure mode).
- For a rebased PR branch, confirm the final range is what you expect before
--force-with-lease(recipe in the playbook).
See also
autonomous-pr-driver— drives a PR to merge-ready; delegates here when a PR is behind base /mergeable=CONFLICTING.git-trunk-branch-and-pr-automation— owns the branch/rebase/squash-merge mechanics this works within.reference/playbook.md— thegit/ghcommand recipes.
Sources
- Git docs:
git-merge/git-rebase(ours vs theirs,--abort),git-rerere,merge.conflictStyle(diff3/zdiff3),git diff --check. https://git-scm.com/docs/git-merge, https://git-scm.com/docs/git-rerere - GitHub PR mergeability fields (
mergeable,mergeStateStatus=DIRTYon conflict): https://docs.github.com/en/graphql/reference/enums#mergestatestatus