[Bug] Merge conflict resolution silently drops closing braces when git hoists shared trailing code outside conflict markers
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
When two branches each add a new code block (method, class, switch case) before a shared closing brace, git's LCS algorithm hoists that shared } outside the >>>>>>> marker. Claude Code resolves the conflict by keeping both branches' code, but treats the shared } as untouchable file context — outputting one closing brace for two code blocks.
The resolved file compiles in some cases but is structurally broken (unbalanced braces). This failure is 100% reproducible on the 1-shared-brace pattern and immune to prompt engineering, including explicit "ensure braces remain balanced" instructions.
Conflicted file (what the model sees):
/// <summary>
<<<<<<< HEAD
/// Alpha utility helper.
/// </summary>
internal static string GetAlphaValue()
{
return "alpha";
=======
/// Beta utility helper.
/// </summary>
internal static string GetBetaValue()
{
return "beta";
>>>>>>> branch_b
} // <-- git hoisted this shared brace outside the markers
What Claude Code produces (WRONG):
internal static string GetAlphaValue()
{
return "alpha";
internal static string GetBetaValue()
{
return "beta";
} // ONE closing brace for TWO methods — broken code
What Should Happen?
Each branch's code block should get its own closing brace:
internal static string GetAlphaValue()
{
return "alpha";
} // Alpha's closing brace
internal static string GetBetaValue()
{
return "beta";
} // Beta's closing brace
Error Messages/Logs
There is no error message — the failure is silent. The Edit tool succeeds, conflict markers are removed, and Claude Code reports the conflict as resolved. The only detectable signal is a brace imbalance in the resulting file:
Brace delta check: { count = N+1, } count = N → delta = +1 (expected: 0)
Every single failure across 19 trials on the hard pattern produces this exact signature: `brace_delta_wrong(expected=+0, got=-1)`.
Steps to Reproduce
1. Create the test repo
mkdir test-repo && cd test-repo
git init -b main
git config user.email "test@test.com" && git config user.name "Test"
git config core.autocrlf false
# Base file
cat > test.cs << 'EOF'
namespace TestNamespace
{
internal static class Utils
{
internal static int Existing() { return 0; }
}
}
EOF
git add . && git commit -m "base"
# Branch A adds AlphaHelper before the namespace close
git checkout -b branch_a
cat > test.cs << 'EOF'
namespace TestNamespace
{
internal static class Utils
{
internal static int Existing() { return 0; }
}
internal static class AlphaHelper
{
public static string GetAlpha() { return "alpha"; }
}
}
EOF
git add . && git commit -m "add AlphaHelper"
# Branch B adds BetaHelper before the namespace close (from main)
git checkout main && git checkout -b branch_b
cat > test.cs << 'EOF'
namespace TestNamespace
{
internal static class Utils
{
internal static int Existing() { return 0; }
}
internal static class BetaHelper
{
public static string GetBeta() { return "beta"; }
}
}
EOF
git add . && git commit -m "add BetaHelper"
# Merge — creates conflict with shared trailing }
git checkout branch_a && git merge branch_b --no-edit
2. Ask Claude Code to resolve
claude "resolve the merge conflict in test.cs"
3. Verify the result
# Check brace balance — should be 0 for correct resolution
python3 -c "
c = open('test.cs').read()
delta = c.count('{') - c.count('}')
print(f'Brace delta: {delta} (expected: 0)')
print('PASS' if delta == 0 else 'FAIL — missing closing brace')
"
Test Data (490 controlled trials, claude-sonnet-4-6)
Failure rate on the shared } pattern
| Pattern | Shared } Count | Trials | Pass Rate |
|---|---|---|---|
| Method-addition, 1 shared } | 1 | 19 | 0% (0/19) |
| Method-addition, 3 shared } | 3 | 10 | 50% (5/10)* |
*The 3-brace variant later improved to 100% with model updates, but the 1-brace variant remains at 0%.
Prompt engineering does not fix it
| Prompt Level | Instruction | Pass Rate |
|---|---|---|
| bare | "resolve merge conflicts" | 0/3 |
| keep-both | + "keep BOTH sets of changes" | 0/16 |
| full-rules | + "ensure braces remain balanced" | 0/3 |
Variables that do NOT cause this failure
| Variable | Test | Result |
|---|---|---|
| File size | 40 trials, 100–28,500 lines | 40/40 PASS |
| Code complexity | 40 trials, real production code vs synthetic | 40/40 PASS |
| Multiple files in one session | 45 trials, single vs batch | 45/45 PASS |
| Conflict position in file | 15 trials, top/middle/end | 15/15 PASS |
Tool strategy is correct
The model always discovers the optimal strategy (grep for markers → partial read → targeted edit → verify). Tool selection is not the issue — the resolution logic within the edit is.
Root Cause Analysis
Git's LCS algorithm identifies the closing } as common to both branches and places it after the >>>>>>> marker. The model has no way to distinguish between:
- A
}that is genuine file context (should appear once) - A
}that was shared between branches (needs to appear once per branch)
The Claude Code system prompt contains exactly one mention of merge conflicts: "typically resolve merge conflicts rather than discarding changes". There are no instructions about shared trailing code, brace balance verification, or git's LCS hoisting behavior.
Suggested Fixes
Option 1 — System prompt instruction: Add merge-conflict-specific guidance about shared trailing code after >>>>>>> markers needing duplication.
Option 2 — Post-edit validation: After an Edit that removes conflict markers from a brace-delimited file, check {count - }count. If unbalanced, inject a system message telling the model to re-examine the area.
Option 3 — Recommend zdiff3: When conflicts are detected, check git config merge.conflictstyle. The zdiff3 format shows the common ancestor, making shared trailing code unambiguous.
Environment
- Claude Code: 2.1.47
- Model: claude-sonnet-4-6
- OS: Windows 11
- Languages affected: Any with brace-delimited blocks (C#, Java, TypeScript, C++, Go, Rust, Swift, Kotlin, Scala)
Claude Model
Sonnet (default)
Is this a regression?
No, this never worked
Last Working Version
_No response_
Claude Code Version
2.1.50
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
Windows Terminal
Additional Information
- The failure is language-agnostic — any brace-delimited language (C#, Java, TypeScript, C++, Go, Rust, etc.) is affected whenever two branches add code blocks before a shared closing brace.
- The model's tool strategy is correct and consistent: it discovers the optimal
grep for markers → partial read → targeted edit → verifypipeline autonomously. The bug is in the resolution logic within the edit, not in tool selection. zdiff3conflict style eliminates the ambiguity entirely. Whengit config merge.conflictstyle zdiff3is set, the common ancestor is shown between|||||||and=======markers, making shared trailing code explicit. This is a zero-cost mitigation users can adopt today.- We ran 490 controlled trials across file sizes (100–28,500 lines), code complexity (real production vs synthetic), conflict counts (1–12), single vs batch file sessions, and three prompt engineering levels. The shared trailing
}pattern is the only variable that causes failure. - No community reports have identified this specific root cause. Related issues exist (claude-code #8287, Aider #800, Sketch.dev blog) but attribute failures to other causes.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗