[Bug] Merge conflict resolution silently drops closing braces when git hoists shared trailing code outside conflict markers

Resolved 💬 2 comments Opened Feb 24, 2026 by bbwolfa Closed Mar 25, 2026

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 → verify pipeline autonomously. The bug is in the resolution logic within the edit, not in tool selection.
  • zdiff3 conflict style eliminates the ambiguity entirely. When git config merge.conflictstyle zdiff3 is 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.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗