skip to content

How do you make GitHub code scanning block a merge, and what breaks when you do?

level: seniorimportance: should knowfreq 44%

answer

  1. Two mechanisms: named checks, or a severity rule
  2. The gate sees new alerts, not the backlog
  3. A check that never runs never passes
  4. Forks cannot write results

basics

~20 s

Require the code scanning results check in branch protection, or add the code scanning rule to a GitHub ruleset with alert-severity thresholds. Both stall pull requests when the analysis is skipped by path filters, cannot run from a fork, or never reports.

solid answer

~50 s

Two mechanisms. The blunt one is **branch protection required status checks**: mark the analysis job and the `Code scanning results` check as required, so a pull request cannot merge until they report success. The precise one is the **code scanning rule in a GitHub ruleset**, which lets you set an `alerts_threshold` and a `security_alerts_threshold` per tool — for example, block on any error-level alert and on security alerts of high or higher, but not on notes. The breakage is always the same shape: a required check that never reports leaves the pull request pending forever. That happens when the workflow is path-filtered and the diff touches nothing it matches, when the pull request comes from a fork so the analysis cannot upload, when the analysis job fails for an infrastructure reason, or when someone renames the job. Gate on new alerts, keep the historical backlog out of the gate, and plan the skipped-run case deliberately.

code

json · 22 lines
json
{
  "name": "main-protection",
  "target": "branch",
  "enforcement": "active",
  "conditions": {
    "ref_name": { "include": ["refs/heads/main"], "exclude": [] }
  },
  "rules": [
    {
      "type": "code_scanning",
      "parameters": {
        "code_scanning_tools": [
          {
            "tool": "CodeQL",
            "alerts_threshold": "errors",
            "security_alerts_threshold": "high_or_higher"
          }
        ]
      }
    }
  ]
}

go deeper

for a junior

Know that a code scanning result can be made a required check, so a pull request cannot merge until the analysis has run and reported.

for a middle

Explain the difference between requiring a named status check and a ruleset rule with severity thresholds, and that the check reports on alerts the pull request introduces.

for a senior

Bring the operational failure modes: path-filtered workflows leaving a required check pending, fork pull requests unable to upload, renamed jobs, and analysis outages stopping all merges.

for a principal

Own the rollout and the escape hatch — observe the gate before enforcing it, choose a threshold where red is always real, and define an audited bypass before an incident forces an unaudited one.

## What "required" means on GitHub A merge gate on GitHub is always a check that must report success on the pull request's head commit. Code scanning participates in two ways. **Required status checks (branch protection or the `required_status_checks` ruleset rule).** You name checks — the analysis workflow's job, and the `Code scanning results` check GitHub creates after an upload — and the branch refuses a merge until each reports success. This is pass/fail only; the gate has no notion of severity beyond whatever the check itself decided. **The code scanning ruleset rule.** Rulesets add a dedicated `code_scanning` rule where, per tool, you set `alerts_threshold` and `security_alerts_threshold`. That expresses "block on error-level alerts and on security alerts rated high or higher, ignore warnings and notes" as policy on the branch, rather than baking it into the tool's exit code. It also blocks while an expected analysis has not yet reported, which is a feature — it means a pull request cannot slip through the window before the scan completes. Rulesets and classic branch protection coexist; a repository can be governed by both at once, and the effective policy is the union of what applies. If you inherit a repository, look in both places before concluding a rule is absent. ## What the check is actually asserting The code scanning results check reports on alerts **the pull request introduces**, not on the repository's whole backlog. This is what makes gating viable on a codebase with a thousand historical alerts: the merge gate is a ratchet on new debt, not a demand to pay off the old debt. Candidates who assume the gate blocks on all open alerts conclude, wrongly, that gating is impossible on legacy code. ## The failure modes **Skipped runs leave the check pending, not passing.** A required check is satisfied by success, and a workflow that never runs never reports. So if the CodeQL workflow uses path filters and someone opens a documentation-only pull request, the required check sits pending and the pull request cannot merge. There is no automatic "skipped counts as passed". The usual answers are: do not path-filter a workflow whose check is required; or pair it with a second workflow that has complementary filters and a job of the same name that succeeds immediately. **Fork pull requests cannot upload results.** The token in a fork-triggered run is read-only, so the SARIF upload fails and no code scanning results check appears. Any repository accepting outside contributions and gating on code scanning must decide what happens to those pull requests — a maintainer-triggered run against a trusted context, or an explicit policy that fork contributions are merged on human review and scanned after. **Renames.** Required checks are matched by name. Rename the job in YAML and the old name stays required and never reports; every pull request hangs until someone notices. **Infrastructure failures become merge outages.** Analysis is the slowest check in most repositories and depends on runners, caches and a working build. When it flakes, nothing merges. If your gate has no documented bypass — a role that can merge without checks, recorded and reviewed — you will get an undocumented one during an incident. **Auto-merge and the pending window.** A pull request with auto-merge enabled waits for the required checks; if the analysis reports after everything else, the gate is doing its job, but people read the delay as the pipeline being broken. Communicating why the merge is waiting matters as much as configuring it. ## Choosing a threshold Gate on something narrow enough that a red gate is always real. Error-level and high-or-higher security alerts are a defensible default; gating on every note-level maintainability finding turns the merge button into a lottery and trains people to look for the bypass. The rule of thumb: if the on-call answer to a red gate is routinely "dismiss it and merge", the threshold is wrong, and you are training the team to dismiss without reading. ## Roll it out in the right order Make the check non-blocking first and watch it for a few weeks. Measure how often it would have blocked, and how many of those blocks were true positives. Only then make it required — first on one important repository, then broadly. A gate introduced before its false-positive rate is known is a gate the organisation will spend the next quarter working around.

  • Why is renaming the analysis job dangerous once its check is required?
    Required checks are matched by name. The old name stays in the policy and nothing ever reports it, so every pull request sits pending while the new job goes green beside it. Rename the job and update the required-check list in the same change, or use a ruleset whose rule does not hard-code a job name.
  • How do you roll out a code scanning gate without a revolt?
    Run it non-blocking first and measure: how often would it have blocked, and how many of those were true positives? Tune the threshold on that evidence, enforce on one important repository, then widen. Introducing a gate before you know its false-positive rate guarantees people spend the next quarter engineering around it.
  • What should happen when the analysis is down and nothing can merge?
    Decide in advance. Either a named role can merge without checks, with the bypass logged and reviewed afterwards, or you accept the outage and say so. What you must not do is leave it undefined — an incident will produce an ad-hoc bypass, and the ad-hoc one is the one nobody reviews.

saying these in an interview costs you the question

  • Assuming a skipped workflow satisfies a required check
  • Thinking the gate blocks on every open repository alert
  • Requiring the analysis job but not the results check
  • Gating on all severities including notes
  • Forgetting a renamed job leaves the old required name pending

context