Open a pull request and you may see something that looks like a contradiction: a green “This branch has no conflicts” line, and right below it a red ✗ on the checks. Both are true at once — because they are answering two completely different questions.
A merge is the moment two lines of development — your branch and the main branch — become one. Before a forge like GitHub lets that happen, it forms an opinion along two independent axes: mergeability (is there a text conflict to resolve?) and checks (did the automated jobs pass?). They show up next to each other on the pull-request page, which is exactly why they get confused for one thing.
Table of Contents
Two questions, not one
Picture two editors marking up the same short document, then handing their copies back to be combined.
The first question is mechanical: did they scribble over the same sentences? If editor A rewrote paragraph two and editor B rewrote paragraph five, you can paste both sets of changes together without either one fighting the other. If they both rewrote paragraph two differently, someone has to decide which wording wins. That is a conflict — and noticing it requires nothing more than lining the two copies up side by side. You do not need to read the document to see that the same lines were touched twice.
The second question is about meaning: after you paste the changes together, does the document still make sense? Two edits can land in different paragraphs — no conflict at all — and still leave the whole thing self-contradictory, because editor A deleted the definition that editor B’s new sentence relied on. To catch that, you actually have to read the combined result and check it against reality.
A conflict check asks whether two edits overlap. CI asks whether the combined result is true.
Software works the same way. The conflict check is the first question — a line-by-line comparison. CI is the second — it reads and runs the combined result.
The conflict check: can these be combined?
When your branch and the main branch have both changed since they parted ways, the forge does a trial merge in memory. It walks the two sets of changes and asks, file by file, line by line: did they touch the same spot? If not, it can weave them together automatically and reports mergeable — no conflicts. If they did touch the same lines in incompatible ways, it reports a merge conflict and refuses to merge until a human resolves it.
Two things are worth knowing about this check:
- It is free and instant. It is just text comparison — no code is compiled or executed. It costs no build minutes and needs no test runner. (You can even run the same trial merge on your own machine with a single git command, entirely offline.)
- It is a hard gate. A forge will not let you complete a merge that has an unresolved conflict. There is no “merge anyway” for conflicts — the ambiguity has to be settled first, because the computer genuinely cannot know which version of the clashing line you meant.
What the conflict check emphatically does not do is run your program. It has no idea whether the code compiles, whether the tests pass, or whether the feature works. “Mergeable” means only “these edits can be stitched together,” not “the stitched-together result is any good.”
The CI check: does the combined result work?
CI — continuous integration — is the second gate, and it is a different kind of thing entirely. CI is a program that takes the merged code and runs it: it installs the dependencies, builds the project, executes the test suite, and enforces whatever rules the project cares about (formatting, security scans, architecture constraints). Then it reports a single ✓ or ✗ as a status check on the pull request.
Because CI actually executes the code, it catches the whole family of problems the conflict check is blind to. The classic one is an integration break:
Here is the classic case. On the main branch, someone renames a function getPrice to getCost. On your branch — in a different file — you add a new call to getPrice. Neither of you touched the same line, so there is no conflict: the forge happily reports “mergeable.” But the merged code now calls getPrice, which no longer exists. Only CI, by running the tests, catches that the combination is broken.
That is the crux of why both gates exist. The conflict check protected you from a textual clash; CI protects you from a behavioral one. Neither can do the other’s job.
Two edits in different files never conflict — and can still break the build together. That gap is exactly what CI is for.
Reading the pull-request page without the confusion
Once you know there are two axes, the pull-request page stops looking contradictory. On GitHub, for example, the two live in separate fields:
| What you see | Which gate | Plain meaning |
|---|---|---|
“This branch has no conflicts” / mergeable: MERGEABLE | conflict | the edits combine cleanly |
“This branch has conflicts” / mergeable: CONFLICTING | conflict | you must resolve overlapping lines first |
| A green ✓ on the checks | CI | the automated jobs ran and passed |
| A red ✗ on the checks | CI | a job ran and failed — or couldn’t run at all |
mergeStateStatus: CLEAN | both | mergeable and checks green |
mergeStateStatus: UNSTABLE | both | mergeable, but a check is failing or missing (not a conflict) |
mergeStateStatus: DIRTY | conflict | there is an actual conflict to resolve |
The pairing that trips people up is mergeable + red check (UNSTABLE). It feels wrong, but it is perfectly ordinary: the edits fit together fine (conflict gate: pass), and separately, a test or rule did not come back green (CI gate: fail). Two gates, two verdicts.
A red check has one more subtlety worth naming, because it hides a very different situation: a check can be red because a test ran and failed, or because the job never ran at all — for instance when a hosted CI service is out of build minutes and the run dies on startup. Same red ✗, opposite meanings. If every check across every branch turns red at the same instant and each “fails” in a couple of seconds, that is almost never your code — it is the runner failing to start. (If you are wondering why a free CI budget drains so fast in the first place, where those minutes actually go.)
So can I just run CI later?
Yes — CI is not a one-shot event welded to the merge button. It is a program that runs against a commit, and you can run it against any commit, any time: re-run the failed job when your build minutes come back, push a fresh commit to re-trigger it, or trigger it by hand. Running it after a merge, on the main branch, is a completely normal thing to do.
The only trade-off is order, and it is about safety, not possibility. The tidy sequence is CI-passes-then-merge, so broken code never reaches the main branch in the first place. If you merge first and run CI later, there is a window where the main branch is unverified — if the delayed run comes back red, you have already merged and now have to fix it forward. Whether that window is acceptable is a judgment call: if the same tests already passed on your own machine, the risk is small; if you are merging something you have not run at all, it is not.
One caveat sits underneath all of this: whether the forge lets you merge on a red check depends on the project’s rules. If a check is marked required, the forge blocks the merge until it is green — you cannot merge-now-and-check-later without an override. If it is only advisory, the red ✗ is a warning, not a lock, and you are free to merge and verify afterward. So “can I run CI later” has two layers: the run itself is always repeatable; whether you may merge before it passes is a policy the repository sets.
The short version
- A merge has two independent gates. The conflict check asks “can these edits be combined?”; CI asks “does the combined result work?” They are not the same question.
- The conflict check is free, instant, and a hard gate. It is a line-by-line text comparison — no code runs — and a forge will not merge over an unresolved conflict.
- CI runs the code. It builds and tests the merged result, so it catches breakages the conflict check cannot see — most classically an integration break, where two non-overlapping edits break each other.
- “Mergeable + red check” is normal, not a bug. The edits fit (conflict gate passed) while a test or rule failed (CI gate failed). On GitHub that reads as
mergeable: MERGEABLE,mergeStateStatus: UNSTABLE. - A red check can mean “failed” or “never ran.” A uniform, two-second red across every branch is a runner/billing failure, not a test failure.
- You can run CI later. The run is always repeatable against any commit; the only question is order (CI-first is safer) and whether the check is required (which decides if you may merge before it is green).
Frequently Asked Questions
Why does my pull request say “no conflicts” but still show a red X?
Because those are two separate gates. “No conflicts” is the mergeability check — your edits and the main branch don’t touch the same lines, so they can be combined. The red X is a CI check — an automated job that builds and tests the code either failed or couldn’t run. Edits fitting together cleanly says nothing about whether the combined code works, which is what CI measures.
What is the difference between a merge conflict and a failing CI check?
A merge conflict is textual: two branches changed the same lines, and the computer can’t know which version you meant, so it refuses to merge until you choose. A failing CI check is behavioral: the merged code was built and run, and something didn’t pass — a test, a lint rule, a build step. The first is caught by comparing text; the second only by executing the code.
Can code merge cleanly and still be broken?
Yes — that is exactly why CI exists. If one branch renames a function and another branch (in a different file) still calls the old name, the two edits don’t overlap, so there’s no conflict and the merge is “clean.” But the combined code now calls something that no longer exists. The tests catch it; the conflict check never could.
Can I run CI after I’ve already merged?
Yes. CI is a program you can run against any commit at any time — re-run the failed job, push a new commit to re-trigger it, or start it manually. The catch is order: running CI before merging keeps broken code off the main branch, whereas merging first leaves a window where the branch is unverified until the later run confirms it.
Does checking for conflicts cost build minutes?
No. The conflict check is a text comparison the forge (or your own machine, via git) does instantly and for free — no build, no test runner, no minutes. Only CI, which actually builds and runs the code, uses build minutes. This is why you can always tell whether a branch is mergeable even when a hosted CI service is completely out of minutes.
What does “mergeable but UNSTABLE” mean on GitHub?
mergeable: MERGEABLE means there is no text conflict — the branch can be combined. mergeStateStatus: UNSTABLE means it is mergeable but at least one check isn’t green (failing or missing). Together they say: “nothing to resolve, but a check hasn’t passed.” That is different from DIRTY, which does mean a real conflict, and from CLEAN, which means mergeable and all checks green.