Skip to content
ExponaBLOG
GIT & GITHUB FOR PROFESSIONAL DEVELOPERS · DAY 094 / 100

Required status checks: a practical guide for developers

Never merge broken code.

Expona Admin7 min read

Required status checks: a practical guide for developers

Code reviews catch obvious bugs, but they can't catch everything. Required status checks ensure that broken code never reaches your main branch by blocking merges until automated tests and other validations pass. This Day 94 guide walks you through selecting the right CI jobs, configuring them as required, and keeping your checks fast enough that developers won't find ways around them.

Why required status checks matter

Without required status checks, a developer can merge code that compiles locally but fails in CI, or passes unit tests but breaks the build in staging. Even with good intentions, humans skip steps under deadline pressure. A required status check removes that choice: the merge button stays grey until the checks turn green.

We've all seen the scenario. A feature branch passes code review, everyone approves, and the developer clicks merge. Three minutes later, the build pipeline fails because a test was flaky, or a dependency wasn't installed correctly. Now the main branch is broken, and the whole team is waiting for a fix. Required status checks prevent this by making the pipeline part of the merge gate itself.

Select CI jobs as required

Not every job in your CI pipeline needs to be required. Selecting the right subset keeps feedback fast while protecting code quality.

Start by identifying which jobs catch the most critical failures. Unit tests, linting, and type checking are almost always required. Integration tests are usually required if they're fast (under 5 minutes). End-to-end tests, security scans, and performance benchmarks might run in CI but often block only if explicitly configured as required. Deployment stages like "deploy to staging" should never be required status checks, since they're side effects, not validations.

The goal is to catch real problems without adding so much latency that developers either abandon the process or start pushing straight to main. If your required checks take 20 minutes, people will find workarounds.

On GitHub, required status checks are configured at the repository level under Settings → Branches → Branch protection rules. Create or edit a rule for your main branch (or whatever you protect), and in the "Require status checks to pass before merging" section, search for the exact job names from your CI provider.

Job type Required? Reason
Unit tests Yes Catch logic errors; fast
Linting Yes Enforce style and catch common bugs
Type checking Yes Catch type errors before runtime
Integration tests Usually Depends on speed; if under 5 min, yes
Build artifact creation Yes Ensure code actually compiles
E2E tests No Too slow; run post-merge or in staging
Security scan Conditional Yes if fast; no if it takes 10+ minutes

Once you've selected your required jobs, make sure each one reports its status back to GitHub. Most CI providers (GitHub Actions, GitLab CI, CircleCI, Jenkins) do this automatically, but verify by opening a draft PR and checking the "Checks" tab.

Block merging until they pass

After selecting which jobs are required, GitHub does the rest: it blocks the merge button until all required checks pass. But there's an important detail: you also want to dismiss stale reviews and require fresh reviews after new commits.

In the same branch protection rule, enable:

  • Require a pull request before merging (with at least 1 approving review)
  • Dismiss stale pull request approvals when new commits are pushed
  • Require status checks to pass before merging (with your selected jobs)
  • Require branches to be up to date before merging (optional but recommended)

The "dismiss stale approvals" setting is subtle but important. If a reviewer approves, then the developer pushes a new commit, that approval becomes void. The developer can't just push again to trigger the same review. They have to wait for a fresh approval, which forces them to run tests again.

Here's what the GitHub API looks like when you programmatically update a branch protection rule to require checks:

bash
curl -X PUT https://api.github.com/repos/YOUR_ORG/YOUR_REPO/branches/main/protection \
  -H "Authorization: token YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "required_status_checks": {
      "strict": true,
      "contexts": ["build", "test", "lint", "type-check"]
    },
    "required_pull_request_reviews": {
      "required_approving_review_count": 1,
      "dismiss_stale_reviews": true
    }
  }'

The strict flag set to true means the branch must be up to date with main before merging. This prevents a scenario where someone merges to main, your PR becomes out of sync, but the old test results still pass.

Keep checks fast and reliable

A slow CI pipeline becomes a productivity tax. If your required checks take 15 minutes, developers lose focus. If they're flaky, people start ignoring failures.

Start by running required checks in parallel. If you're using GitHub Actions, split your test suite across multiple jobs. A NestJS or Next.js app can run unit tests, integration tests, build, and linting at the same time. Set up a final "gate" job that depends on all of them:

bash
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
      - run: npm run lint

  type-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
      - run: npm run type-check

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
      - run: npm run test:unit

  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
      - run: npm run build

  all-checks-pass:
    needs: [lint, type-check, test, build]
    runs-on: ubuntu-latest
    steps:
      - run: echo "All checks passed"

This structure lets you report a single required status check (all-checks-pass) to GitHub, while still running everything in parallel. If lint fails, the developer sees it immediately alongside the other results.

Second, cache dependencies aggressively. Node modules, Maven dependencies, and Docker layers should be cached. GitHub Actions caching and Docker layer caching can cut 5 minutes off a build:

bash
- uses: actions/cache@v3
  with:
    path: ~/.npm
    key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-node-

Third, be ruthless about removing slow tests from the required set. Slow integration tests or E2E tests belong in a separate pipeline that runs post-merge or on a schedule. Required checks should finish in 3–5 minutes at most.

Fix flaky tests quickly

A flaky test—one that sometimes passes and sometimes fails for the same code—is worse than a slow test. It breaks trust in the entire validation system. Developers start clicking "Re-run" instead of investigating, and the required check becomes theater.

When you notice a flaky test, treat it as a production bug. Investigate immediately:

  1. Check if it's timing-dependent (async waits, sleep statements). Add proper sync mechanisms like waitFor in Cypress or waitUntil in Playwright.
  2. Check if it relies on external services (databases, APIs, caches). Mock or stub them, or use Docker containers (testcontainers pattern) to run dependencies locally.
  3. Check if it's order-dependent. Flaky tests often fail because they run in a different sequence or share state. Run your test suite in random order to flush these out.
  4. Check for race conditions or uncleared global state. Database transactions, environment variables, and mocked modules can leak between tests.

In a React or Next.js project, a common culprit is waiting for async operations without proper assertions. Instead of:

bash
// Bad: relies on timing
await new Promise(r => setTimeout(r, 500));
expect(screen.getByText('Data loaded')).toBeInTheDocument();

Use:

bash
// Good: waits for the element
await screen.findByText('Data loaded', {}, { timeout: 5000 });

Flaky tests should disable merge until you fix them. If a test is flaky, remove it from the required checks temporarily, fix it in a separate PR, and add it back only when it's reliable.

Common mistakes

Mistake 1: Required checks that are too slow. If your required checks take 20+ minutes, developers will feel pressure to bypass them or merge without waiting. Keep them under 5 minutes. Move long tests to a separate post-merge pipeline.

Mistake 2: Forgetting to require checks on protected branches. You can configure required checks, but if you don't enable branch protection rules, developers can still push directly to main. Always pair required checks with branch protection.

Mistake 3: Not dismissing stale reviews. Without this setting, a developer can get approval, push new code, and merge without fresh review. Always enable "dismiss stale pull request approvals."

Mistake 4: Letting flaky tests slide. A test that fails 10% of the time is the same as no test. People will ignore it, and your whole validation system loses credibility. Fix flaky tests immediately or remove them.

Mistake 5: Requiring checks that don't report status. If a job in your CI pipeline doesn't report its result back to GitHub as a commit status, it can't be a required check. Verify that your CI provider is sending status updates.

Key takeaways

  • Select the fastest, most critical jobs (unit tests, linting, type checking, build) as required status checks.
  • Block merging on main until all required checks pass, not just code review approvals.
  • Run required checks in parallel and cache aggressively to keep feedback time under 5 minutes.
  • Treat flaky tests as production bugs. Investigate and fix them immediately, or remove them from the required set.
  • Pair required status checks with branch protection rules that dismiss stale reviews and require branches to be up to date.
  • Remove slow tests (E2E, performance) from the required set. Run them post-merge or on a schedule instead.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles