CI/CD

Required status checks: a practical merge-gate guide

Most teams believe their CI blocks merges. It usually doesn’t. Checks that merely report are advisory, and advisory red is an opinion, not a barrier. This guide covers the difference, the branch-protection setup that makes checks binding, the naming pitfalls that silently void your gate, and the escape hatches to design before the 2 a.m. incident.

Pratik Rana 8 min read

01A green pipeline nobody waits for

The failure mode is always the same story. CI runs on every pull request and posts its little green checkmark. But nothing enforces it: someone merges while the run is still in progress, someone else clicks through a red X because “it’s probably flaky”, and main drifts into a state no commit ever tested. The pipeline exists; the gate doesn’t. Advisory checks measure quality culture — required checks enforce it.

The distinction lives in branch protection, not in your workflow file. GitHub Actions can only report results; whether those results block anything is decided by rules attached to the branch. That separation confuses people once and then costs them years of false confidence, so let’s make the enforcement layer explicit. If you haven’t built the underlying test job yet, start with our Playwright on GitHub Actions guide, then come back and make it binding.

02Branch protection in five minutes

In repository settings, under Branches (or Rules → Rulesets on newer plans), create a rule targeting main. Three switches matter most: require a pull request before merging, require status checks to pass, and select which checks are required from the list of checks that have recently run. Via CLI, the same configuration is one API call:

gh api repos/{owner}/{repo}/branches/main/protection -X PUT \
  --input - <<'JSON'
{
  "required_status_checks": {
    "strict": true,
    "contexts": ["e2e / verdict", "lint"]
  },
  "enforce_admins": true,
  "required_pull_request_reviews": {
    "required_approving_review_count": 1
  },
  "restrictions": null,
  "allow_force_pushes": false,
  "allow_deletions": false
}
JSON

Two settings deserve explanation. "strict": true requires the branch to be up to date with main before merging — it closes the “tested against stale base” hole where two compatible-looking PRs merge independently and only their combination is broken. "enforce_admins": true applies the rules to administrators too; leave it off and you’ve built a gate with a permanent, temptation-shaped hole in it. Note the contexts list: required checks must be registered by exact name, which is where most gates quietly fail — more on that below.

03Advisory versus required, precisely

An advisory check runs and reports. Anyone with write access can merge past a failing advisory check; depending on settings they may even merge before it finishes. Nothing about this is visible in the UI as dangerous — the red X looks identical whether or not it has teeth. A required check, by contrast, changes what the merge button does: the PR cannot merge until every required context reaches success. Not “completed” — succeeded. Skipped jobs count as neither, which is why a required job guarded by if: conditions can leave a gate permanently stuck rather than safely passing.

Choose per check, not globally. Require what verifies correctness of the diff: build, lint, unit tests, critical-path E2E. Keep long-tail suites advisory or scheduled — a 40-minute exhaustive run as a hard requirement converts every typo fix into a coffee break and teaches the team to hate the gate. Tiering by cost is exactly the strategy behind continuous testing across the pipeline: fast blocking feedback on PRs, deep coverage on schedule.

04The naming trap

Required contexts match check-run names byte-for-byte, and several common patterns change names invisibly. Matrix jobs append their matrix values: a job test sharded four ways produces e2e / test (1) through (4) — requiring plain e2e / test protects nothing. Renaming a workflow file changes names. Display-name edits change names. Each rename leaves the old required context pointing at a check that will never run again, which means the gate either wedges every PR or — if someone “fixes” it by removing the requirement — stops existing.

The robust pattern is an aggregator: a final job that runs after all shards and succeeds only if every shard succeeded. Require the aggregator’s single stable name, and future shard-count changes never touch branch protection again. External systems integrate the same way — Guard, for instance, posts its pass/fail/flaky verdict as a native check run precisely so it can occupy a slot in the required list like any first-class job (see the GitHub Actions integration).

05A gate is only as strong as its flake rate

A required check that fails randomly 10–20% of the time doesn’t reduce risk — it trains bypass. Engineers learn that red usually means “roll the dice again”, and within a quarter the team is merging through admin override reflexively. Flake budget is gate security.

Before making a suite required, earn it: cap retries at one, track which tests consume them, and move repeat offenders to a quarantine lane that still runs and reports but doesn’t block, with a named owner and expiry date. Review flake rate weekly like an SLO; under 1% is a realistic target. Diagnosis comes before policy — timing races, shared state, third-party outages, and environment drift each need their own fix, laid out in our flaky-tests guide. A merge queue pairs well here: with strict mode plus a queue, each PR is validated against the true tip of main, serializing the small window where independent branches would otherwise conflict untested.

06Escape hatches you design on day one

Hard gates meet reality at the worst time — production is down, the fix is three lines, and CI takes twelve minutes you don’t have. Decide the protocol now, calmly, instead of improvising during an incident. The standard toolkit: an admin-override path that works but logs loudly (audit entries are reviewable later); a small, named bypass list for on-call rotations rather than “everyone”; and a written rule that any bypass triggers an immediate follow-up — revert forward, land the green fix behind it, and post the incident note. Some teams invert the pressure entirely: allow bypass only via a dedicated hotfix branch whose protection is slightly softer, keeping main’s gate absolute.

Whatever you choose, measure it. Bypass frequency is a leading indicator: rising overrides mean either your suite got slower, your flake rate climbed, or your process stopped fitting reality — all worth knowing within days rather than quarters. And when the required E2E verdict itself needs richer evidence than a checkmark, deterministic replay with full DOM, network, and clone state turns “why did the gate go red?” from a meeting into a link. That’s the model Molar is built around: gates that stay honest because every failure arrives with its evidence attached. For the broader rollout sequence, see regression testing strategies.

A merge gate with evidence attached

Molar reports pass, fail, and flaky as distinct GitHub check runs, so your required-status gate stays meaningful — and every red verdict links to a full replay.