Use when git reports merge conflicts, rebase conflicts, cherry-pick conflicts, or revert conflicts, or when the user asks to resolve conflicts. Triggers: "fix conflicts", "resolve conflicts", "merge conflicts", "rebase conflicts", "fix merge", "resolve merge", "help with conflicts", "conflict markers".
Announce: "I'm using the fix-conflicts skill to resolve merge conflicts."
Run the conflict state detector:
./scripts/get-conflict-state.sh
The output shows the operation type (merge, rebase, revert, cherry-pick), conflicted files with their conflict types, and branch context.
If Operation is "none" and no conflicted files: inform the user there are no conflicts to resolve and stop.
If $ARGUMENTS specifies a file: limit resolution scope to that file only (verify it appears in the conflicted list).
Read every conflicted file listed in Step 1. For large files (>500 lines), focus on sections containing conflict markers (<<<<<<<, =======, >>>>>>>).
Then gather history context to understand what each side intended:
<interaction> AskUserQuestion( header: "Keep or delete?", question: "<file> was modified on one side but deleted on the other. The modification: <brief description>. The deletion was in commit <sha>: <message>. The deletion appears <intentional|incidental>. Keep the modified version or delete?", options: [ "Keep modified" -- Stage the modified version with git add, "Delete" -- Stage deletion with git rm ] ) </interaction> <interaction> AskUserQuestion( header: "Conflict choice", question: "Conflict in <file>: <brief description of both sides>. How should this be resolved?", options: [ "Keep local (ours)" -- Use the local/HEAD version, "Keep remote (theirs)" -- Use the incoming version, "Merge both" -- Combine changes from both sides, "Leave unresolved" -- Skip this file for manual resolution ] ) </interaction> <interaction> AskUserQuestion( header: "Conflict choice (rebase)", question: "Conflict in <file> while replaying your commit '<commit message>'. Upstream (HEAD) has: <ours summary>. Your branch has: <theirs summary>. How should this be resolved?", options: [ "Keep your branch change (theirs)" -- Preserve your feature branch work, "Keep upstream (ours)" -- Discard your branch's version for this section, "Merge both" -- Combine both sides, "Leave unresolved" -- Skip this file for manual resolution ] ) </interaction># Local commits that touched conflicted files
./scripts/get-conflict-history.sh local
# Remote commits that touched conflicted files
./scripts/get-conflict-history.sh remote
For each commit listed in the history output, inspect its actual diff to understand intent:
# Show the full diff for a specific commit touching the conflicted file
git show <commit-sha> -- <conflicted-file>
Pay attention to:
For each conflict, determine:
| Question | How to answer |
|---|---|
| What did the local side change? | git show each local commit touching the file; inspect code between <<<<<<< HEAD and ======= |
| What did the remote side change? | git show each remote commit touching the file; inspect code between ======= and >>>>>>> |
| Did either side intentionally remove code? | Check if the removal was in a dedicated commit (refactor/cleanup/deprecation) vs. incidental to a larger change |
| Are changes independent? | Different logical concerns = keep both |
| Are changes contradictory? | Same logic changed differently = need judgment |
During git rebase, ours/theirs are reversed from merge:
| Side | Merge | Rebase |
|---|---|---|
<<<<<<< HEAD (ours) | Your branch | Upstream (e.g., main) |
======= → >>>>>>> (theirs) | Incoming branch | Your feature branch |
The code between ======= and >>>>>>> is YOUR feature branch work.
Never discard it without explicit user confirmation — doing so silently loses changes you wrote.
Resolution posture during rebase:
| Situation | Strategy |
|---|---|
| Changes are independent (different functions, different lines) | Merge both — keep all changes |
| Changes overlap but complement each other | Combine intelligently — integrate both intents |
| Changes contradict each other | Ask the user (see "When uncertain" below) |
| One side is strictly newer/better | git show both sides' commits — only keep one side if the other's changes are already included or purely cosmetic. During rebase, verify "better" is not just upstream recency. |
<<<<<<<, =======, >>>>>>>).Before writing the resolved version of any conflict, complete this checklist:
git show analysis).git show the commit that performed the removal.| Rationalization | Reality |
|---|---|
| "I'll keep both sides to be safe" | Re-adding intentionally deleted code is not safe — it reverses a deliberate decision |
| "The other side still has it, so it must be needed" | The other side's branch may predate the removal |
| "One side is newer so I'll take that whole block" | The older side may contain additions the newer side never saw |
Taking an entire side (--ours / --theirs or keeping one conflict block verbatim) is only safe when
the other side's changes in that region are trivially reconcilable.
| Safe to take one side wholesale | NOT safe — must merge manually |
|---|---|
| Other side's changes are formatting/whitespace only | Both sides have substantive logic changes |
| Other side only reordered imports | Both sides added new code in the same region |
| Conflict is in a generated or lock file (will be regenerated) | One side removed code the other side modified |
| One side's change is a strict subset of the other | Both sides changed function signatures differently |
Before selecting a whole side, run git show <commit> -- <file> for the side you would drop
and confirm its changes are present elsewhere or are not meaningful.
# Find the commit that deleted the file on the deleting side
git log --diff-filter=D --oneline -1 -- <file>
# Examine the commit to understand why
git show <deletion-commit-sha>
| Deletion context | Resolution |
|---|---|
| Dedicated cleanup/refactor commit (message says "remove", "deprecate", "delete", "clean up") | Deletion was intentional — default to deleting unless the modification is critical new work |
| Feature commit that reorganized code (moved, not removed) | Check where the code moved to; merge the modification into the new location |
| Incidental to a large commit with no mention in the message | Ask the user — deletion may have been accidental |
git add <file>git rm <file>Do NOT guess about business logic. Ask the user.
For merge conflicts:
For rebase conflicts (ours = upstream, theirs = your feature branch):
When a version field or changelog conflicts, the upstream (main) side has released a new version. NEVER merge version entries. The upstream release history is immutable.
Resolution rule: Keep all upstream version entries and release notes exactly as they are. Bump your version to be strictly above the upstream version, then add your changelog entry as the newest.
| File | How to resolve |
|---|---|
Version fields (package.json version, plugin.json version, Cargo.toml version, pyproject.toml version) | Take the upstream version. If your version was equal or higher, bump to the next appropriate semver above upstream (e.g., upstream released 1.3.0, your branch had 1.3.0 -> bump to 1.4.0). |
| Changelogs (CHANGELOG.md, HISTORY.md, CHANGES.md) | Keep ALL upstream entries untouched. Add your entry above the upstream's latest, with the new bumped version. |
| Release manifests (versions.json, lerna.json, release-please config) | Same as version fields: upstream wins, then bump yours above. |
| Rationalization | Reality |
|---|---|
| "Both versions are the same, so I can keep either" | The upstream entry represents a published release. Your entry represents unreleased work. Bump yours. |
| "I'll merge the changelog entries under one version" | Each version in a changelog represents a distinct release. Combining them rewrites release history. |
| "My version is higher, so I should keep mine" | Upstream was released first. Its version is now occupied. Bump yours above it. |
| File type | Resolution approach |
|---|---|
| Lock files (package-lock.json, pnpm-lock.yaml, yarn.lock, Gemfile.lock, go.sum) | Accept either version, then regenerate: delete the lock file, run the package manager install, stage the new lock file |
| Generated files (.pb.go, .generated.ts, compiled assets) | Accept either version, then regenerate using the project's build command |
| Import conflicts | Keep both imports, remove duplicates, respect project ordering conventions |
| Formatting-only conflicts | Apply project formatting conventions consistently |
After resolving each file:
# For kept/merged files
git add <file>
# For deleted files
git rm <file>
Run in parallel:
# Confirm no conflict markers remain in the entire repo
git diff --check
# Verify all conflicts are resolved (should show no unmerged paths)
git status
If conflict markers remain: go back to Step 3 for the affected files.
For every resolved conflict (not just during rebase), verify that both sides' meaningful changes survived:
During rebase — additional check for the commit being replayed:
Get the SHA of the commit currently being applied:
git_dir=$(git rev-parse --git-dir)
cat "$git_dir/rebase-merge/stopped-sha" 2>/dev/null \
|| cat "$git_dir/rebase-apply/original-commit" 2>/dev/null
Then inspect what that commit intended:
git show <stopped-sha> --stat
git show <stopped-sha> -- <conflicted-file>
Confirm the intent of the feature branch commit is present in the resolved version.
| Rationalization | Reality |
|---|---|
| "I checked the conflict markers, that's enough" | A conflict can be syntactically resolved but semantically wrong — one side's intent may be missing |
| "The merge compiled, so it's correct" | Compilation doesn't verify that both sides' behavioral changes are present |
| "I kept both sides' code so nothing was lost" | Keeping both sides can re-add intentionally deleted code, which is also a loss of intent |
Detect and run type checker or linter to catch semantic conflicts
(code that merges cleanly but is logically broken —
e.g., a function signature changed on one side while the other side calls it with old arguments).
Detection: check for tsconfig.json (-> npx tsc --noEmit), mypy.ini/pyproject.toml with mypy config (-> mypy),
Cargo.toml (-> cargo check), or a lint target in the Makefile (-> make lint). If none found, skip.
Provide a summary:
Resolved N conflict(s):
- <file1>: <brief resolution description>
- <file2>: <brief resolution description>
Unresolved (if any):
- <file3>: left for manual resolution
To continue:
merge: git merge --continue
rebase: git rebase --continue
cherry-pick: git cherry-pick --continue
revert: git revert --continue
NEVER automatically run git merge --continue, git rebase --continue, git cherry-pick --continue, git revert --continue, or git commit after resolving conflicts.
The user must review the resolutions and continue the operation manually.
| Rationalization | Reality |
|---|---|
| "All conflicts are resolved, so it's safe to continue" | The user may want to review resolutions, run tests, or abort |
| "The user asked me to fix conflicts, which implies continuing" | Fixing conflicts and continuing the operation are separate actions |
| "It saves the user a step" | An unwanted merge commit or rebase is harder to undo than typing one command |
| Error | Action |
|---|---|
| Not a git repository | Inform the user and stop |
| No conflicts detected | Inform the user there are no conflicts to resolve. Stop. |
| Conflict state script fails | Fall back to git status and git diff --name-only --diff-filter=U for manual detection |
| File too large to read | Focus on conflict marker sections using Grep to find <<<<<<< line numbers, then Read with offset/limit |
| Semantic conflict (compiles but logically wrong) | Flag the risk to the user: "These files merged cleanly but may have semantic conflicts — recommend running tests" |
| Lock file regeneration fails | Report the error. Suggest the user run the package manager manually. |
| User chooses "Leave unresolved" | Track the file and include it in the unresolved list in Step 6 |