Why can a branch that passed CI still break main when merged, and what limits that?
answer
- Ask which tree was actually verified
- Git compares text, not meaning
- Clean merge, broken build
- The risky window is merge base to merge
- Shorten the window or test the merged result
basics
~20 sBecause the branch was verified against its own commits on an older base, not against the merged result. Changes that landed on main since the branch's merge base are never combined with it until the merge happens, so a clean textual merge can still be logically broken.
solid answer
~60 sVerification runs on some tree; the question is which. A branch tested at its own tip has been verified against the trunk as it was at the branch's merge base, plus the branch's own commits. Anything merged into the trunk since that merge base has never coexisted with the branch's code. Git will still merge cleanly whenever the two sides touched different lines — Git compares text, not meaning — so a rename on the trunk and a new caller of the old name on the branch merge without a conflict and break at build or run time. This is a semantic conflict. Two things limit it: keep the branch short, so the window between merge base and merge is small and little can land in it; and verify the merged result rather than the branch tip, by updating the branch from the trunk immediately before merging and re-running the checks on that state. Trunk-based development attacks the problem mainly through the first, by making the window hours instead of weeks.
code
bash · 6 linesgit log --oneline $(git merge-base main topic)..main
git switch topic
git merge main # or: git rebase main
# re-run the checks on THIS tree, then land it
git switch main
git merge --ff-only topicgo deeper
Know that Git merges text, not meaning, so two changes can combine without conflict and still break the build — and that testing the branch alone does not test the merged result.
Explain the merge base and why the branch's checks never saw trunk changes made after it. Describe updating the branch from the trunk and re-running checks before merging.
Bring the production judgment: name semantic conflicts with a concrete example, quantify the risk as the size of the merge-base-to-merge window, and treat restoring a broken trunk as higher priority than diagnosing it.
Own the tradeoff between integration throughput and pre-merge certainty — how much serialisation is worth its cost, what branch-age limit the team commits to, and being explicit that no policy removes semantic conflicts, only shrinks the window.
## The gap between what was tested and what lands Every automated check runs against a specific tree. When a check runs on a topic branch's tip, that tree contains the trunk as it stood at the branch's **merge base** plus whatever the branch added. It does not contain anything merged into the trunk since. Those changes and the branch's changes have simply never been in the same tree. The merge result is a third tree that has never existed anywhere until the merge produces it. Passing on the branch is therefore evidence about a different tree than the one you are about to publish. ## Why Git does not catch it Git's merge is textual and structural. It compares each side against the merge base and combines non-overlapping changes; it reports a conflict only when both sides changed the same region, or when file-level operations collide. It has no model of your language, your types, or your call graph. So the failing cases are precisely the ones where both sides are individually valid and touch different text: - The trunk renames a function or changes its signature; the branch adds a new call to the old form. Different files, no conflict, broken build. - The trunk tightens a validation rule; the branch adds a code path that produces data the new rule rejects. Compiles fine, fails at run time. - The trunk removes a configuration key; the branch adds a reader for it. - Both sides add something with the same name in different files — a route, a migration identifier, a translation key. Each merges cleanly, the pair collides at run time. These are **semantic conflicts**: the merge is textually correct and logically wrong. They are the reason "it merged cleanly" is not a statement about correctness. ## The two levers **Shorten the window.** The set of trunk changes that can conflict semantically with a branch is exactly the set that landed between the branch's merge base and its merge. If a branch lives two hours, that set is nearly empty and the risk is negligible. If it lives six weeks, the set is everything the team did for six weeks. This is the lever trunk-based development pulls, and it is why the model's benefit is not really about avoiding textual conflicts — it is about shrinking the interval in which anything can go stale. **Verify the merged state, not the branch tip.** Before merging, bring the branch up to date with the trunk — merge the trunk into it or rebase it onto the trunk — and re-run the checks on that result. Now what was verified is the tree you are publishing. The residual gap is the race between that verification and the merge: if someone else lands a change in between, you are stale again. Shorter verification and fewer concurrent merges shrink that race; some hosting platforms provide serialisation for it, but that is their mechanism, not Git's. Note the ordering property this gives you. If the branch is a fast-forward away from the trunk at merge time, the merged result is bit-identical to the branch tip you tested — the strongest guarantee available without extra machinery. That is one reason teams like `git merge --ff-only` for landing an already-updated branch. ## What trunk-based development actually buys A candidate should be careful not to overclaim. Trunk-based development does not eliminate semantic conflicts; it makes them small, rare and cheap to diagnose. When a branch is a day old and the trunk breaks after merging it, the suspect set is one day of commits and the fix is usually obvious. When the branch is two months old, the same failure is an investigation. The second thing it buys is direction: failures surface while both changes are fresh in their authors' heads, rather than at the end of a long branch when the other author has moved on. ## Handling the break When the trunk does break, the trunk-based instinct is to restore it immediately — usually by reverting the merge, which is a small operation precisely because the merge was small — and then fix forward on a new short branch. A broken trunk blocks everyone else's integration, so its priority is above the change that broke it. ## Strong answer checklist Name the mechanism (verification ran against a stale merge base), name the failure class (semantic conflict, clean merge and broken result), give a concrete example, then give both levers and be honest that neither is a guarantee — only a reduction in window size.
- Give a concrete semantic conflict that Git merges without complaint.The trunk renames a function and updates every existing caller; your branch, cut before that, adds a brand-new call to the old name in a different file. Neither side touched the other's lines, so there is no textual conflict, and the merged tree fails to compile. Git compared regions of text, not the call graph.
- Does updating your branch from the trunk before merging fully close the gap?No, it narrows it to the race between your verification and the merge itself. If someone lands another change in that interval, your verified tree is stale again — just by minutes rather than weeks. Shorter check times and fewer simultaneous merges reduce the exposure; nothing inside Git eliminates it.
- What is the first move when a merge does break the trunk?Restore the trunk before diagnosing — usually by reverting the merge, which is cheap because the merge was small — then fix forward on a new short branch. A broken trunk blocks every other developer's integration, so its priority outranks the change that caused it.
saying these in an interview costs you the question
- Says a clean merge means the result is correct
- Thinks CI on the branch tip covers the merged state
- Believes rebasing alone prevents semantic conflicts
- Claims trunk-based development eliminates integration failures
- Leaves a broken trunk while debugging the cause