Resolve merge conflicts
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.From its SKILL.md
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.
2 things 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.
- runs commandsInstructs the agent to run 8 commands, including `git merge --abort` and 7 more.
SKILL.md
8.0 KB, ~1.9k tokens by cl100k_base, 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
What ships with it: 1 file
5.9 KB alongside SKILL.md
reference/
- playbook.md5.9 KB