---
title: "Semantic merge conflicts: when the merge is clean but the code breaks · Cork"
description: "A semantic merge conflict is a clean git merge that breaks the program. Why git misses them, examples with AI agents, and how to catch and prevent them."
canonical: "https://corkcli.com/blog/semantic-merge-conflicts"
last_updated: "2026-11-14"
published: "2026-10-03"
author: "Kevin Vera"
---

# Semantic merge conflicts: when the merge is clean but the code breaks

A semantic merge conflict is a clean git merge that breaks the program. Why git misses them, examples with AI agents, and how to catch and prevent them.

By Kevin Vera · October 3, 2026 · [Guides](https://corkcli.com/blog/c/guides) · 6 min read

A [semantic merge conflict](https://corkcli.com/blog/semantic-merge-conflict) is when two branches merge without a single git conflict, yet the combined code is broken: one branch renamed a function while the other added a call to the old name, or one changed what a function returns while the other started relying on the old behavior. Git cannot see these because it merges text, not meaning. You catch them by building, type checking and testing the merged result before it reaches main, and you prevent them by integrating often and making breaking changes in a way the other branch can survive.

## Why git misses them

Git's three-way merge compares each branch against the common ancestor, line by line. If the two branches changed different lines, or different files, git combines them and reports success. It has no idea that a line in `checkout.ts` calls a function whose name changed in `pricing.ts`.

Martin Fowler's description of the problem uses exactly this case: one developer renames `clcBl` to `calculateBill` with a refactoring tool that updates every caller, while a teammate "adds more calls to this method on his feature branch." Textually, nothing overlaps. Semantically, the merged program calls a method that no longer exists.

GitHub's branch protection docs describe the same risk from the CI side: if a branch does not have to be up to date before merging, "Status checks may fail after you merge your branch if there are incompatible changes." Each branch passed its checks alone; the combination was never tested.

## Common examples with parallel AI agents

Parallel agents are especially prone to these, because each one works from a snapshot and none sees the others' changes until merge.

| Branch A (agent 1) | Branch B (agent 2) | Merged result |
|---|---|---|
| Renames `getUser` to `fetchUser` and updates all existing callers | Adds a new call to `getUser` in a new file | Build or runtime error |
| Adds a required `tenantId` parameter to `createInvoice` | Adds a call with the old arguments | Type error, or wrong behavior in untyped code |
| Changes `timeout` from seconds to milliseconds | Sets `timeout: 30` in new config | Requests time out after 30 ms |
| Makes `findOrder` return `null` instead of throwing | Writes code that relies on the throw | Unhandled null downstream |
| Adds migration `0042_add_index.sql` | Adds migration `0042_add_column.sql` | Two migrations with one number |
| Removes an unused-looking config key | Starts reading that key | Silent default at runtime |

The first two are caught by a compiler or type checker on the merged code. The others need tests, or a reviewer who knows both changes.

## How to catch them

**1. Build and test the merged result, not just each branch.** Before landing an agent's branch, rebase it onto the latest main and run the full suite:

```bash
git fetch origin
git rebase origin/main
# compile or type check, then run tests
```

For TypeScript, `tsc --noEmit` type checks without writing output, which makes it a fast first gate for renames and signature changes. Most statically typed languages give you the equivalent through a normal build.

**2. Test several agent branches together.** When multiple agent branches are waiting, merge them into a throwaway branch and test the combination:

```bash
git switch -c integration-check origin/main
git merge --no-edit agent/search agent/billing agent/onboarding
# build and run tests here
```

If the textual merge fails, `git merge-tree --write-tree --name-only` can tell you which files conflict without touching your working tree. If it succeeds, the tests tell you about the semantic ones.

**3. Make CI enforce it.** On GitHub, turn on "Require branches to be up to date before merging" for the protected branch, or use a merge queue, which builds a temporary branch from the latest base plus the changes ahead in the queue and runs your required checks on that. If you use GitHub Actions, the workflow must listen for the `merge_group` event.

## How to prevent them

Catching is good; not creating them is better.

- **Integrate often.** Fowler's other remedy is frequent integration: the sooner branches meet, the less code is built on outdated assumptions. Keep agent branches small and land them quickly.
- **Make renames survivable.** When an agent renames a widely used function, have it keep the old name as a thin deprecated alias until other branches have landed, then remove it in a follow-up.
- **Add parameters with defaults.** A new optional parameter breaks no existing or in-flight callers; a new required one breaks all of them.
- **Write down contract changes.** If an agent changes units, return types or error behavior, it should say so in the commit message and in a note other agents read, such as a shared `AGENTS.md` section or a handoff file.
- **Avoid numbered-file races.** For migrations and similar sequences, have one agent own them, or use timestamped names.

## How Cork helps

All of the above detects a semantic conflict after both sides are written. The cheaper moment is while the second agent is still working, before it writes the call to the old name. Cork is the [coordination layer](https://corkcli.com/blog/coordination-layer-ai-coding-agents) for multiplayer coding agents: it keeps Claude Code, Codex and Cursor agents, on one machine or across a team's machines, from overwriting each other on one codebase, and hands each agent what the others already learned.

For semantic conflicts specifically:

- **Live awareness:** when another agent renames a function or changes a signature that your agent depends on, your agent hears it on its next step.
- **[Semantic lint](https://corkcli.com/blog/semantic-lint):** that rename or signature change becomes a lint rule your agents are checked against, so a new call to the old name gets flagged.
- **Briefing:** each agent starts with the exact signatures, callers and recent changes of the code it is about to touch.
- **Landing:** `git push` becomes one step that rebases onto teammates' commits, reruns the tests and pushes, so the merged result is tested before it lands.
- **MCP tools:** `get_symbol` and `blast_radius` are available to any MCP client through Cork's MCP server.

Cork Free covers one machine with no account; the paid plan adds teammates' machines. Install:

```bash
curl -fsSL https://corkcli.com/install.sh | sh
```

## Frequently asked questions

### What is a semantic merge conflict?

A merge that git completes without conflicts, where the combined code is still wrong because the two branches made incompatible assumptions, such as a rename on one side and a new call to the old name on the other.

### Can git detect semantic conflicts?

No. Git merges text. A compiler, type checker or test suite run on the merged result is what detects them.

### Why do passing branches fail after merging?

Each branch was tested against the old main, never against the other branch. Requiring branches to be up to date, or using a merge queue, tests the combination before it lands.

### Do worktrees prevent semantic conflicts?

No. Worktrees keep agents' files apart while they work, which can make semantic conflicts more likely, since neither agent sees the other's changes until merge.

## Sources

- <https://martinfowler.com/bliki/SemanticConflict.html>
- <https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches>
- <https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-a-merge-queue>
- <https://git-scm.com/docs/git-merge-tree>
- <https://www.typescriptlang.org/tsconfig/#noEmit>

## Related reading

- [Semantic merge conflict: changes that merge cleanly but break the code](https://corkcli.com/blog/semantic-merge-conflict): A semantic merge conflict is when two changes merge with no textual conflict but the result is wrong, such as a rename plus new calls to the old name.
- [Why AI coding agents cause merge conflicts, and how to prevent them](https://corkcli.com/blog/why-ai-coding-agents-cause-merge-conflicts): Parallel AI coding agents create merge conflicts through stale bases, shared hotspots and drive-by edits. Here is why, and seven concrete ways to prevent them.
- [Agent merge conflict: when parallel AI agents' branches will not merge](https://corkcli.com/blog/agent-merge-conflict): An agent merge conflict is a git merge conflict between branches written by AI coding agents running in parallel, usually found only when the work is combined.
- [How to review pull requests from several AI agents at once](https://corkcli.com/blog/review-pull-requests-from-multiple-ai-agents): Review pull requests from several AI coding agents at once: triage with gh, review for cross-PR conflicts, use CODEOWNERS, AI review passes and a merge queue.

[All articles](https://corkcli.com/blog.md) · [Cork in Markdown](https://corkcli.com/index.md) · [llms.txt](https://corkcli.com/llms.txt)

This page in HTML: https://corkcli.com/blog/semantic-merge-conflicts
