A CI pipeline that runs tests is only useful if it can block bad code from reaching the main branch. Quality gates are enforced checks that must pass before a pull request can be merged. Without them, developers can ignore failing tests and merge anyway.
Branch Protection Rules
GitHub lets you protect branches with rules that enforce your team's standards. Navigate to your repository's Settings > Branches > Add branch protection rule.
Key settings for the main branch:
- Require a pull request before merging: No direct pushes to main. All changes go through PRs.
- Require status checks to pass before merging: Select the CI jobs that must succeed.
- Require branches to be up to date before merging: The PR branch must include the latest changes from main.
- Require review approval: At least one team member must approve the PR.
- Do not allow bypassing the above settings: Even admins must follow the rules.
Required Status Checks
Status checks are the CI jobs that must pass. When you configure them in branch protection, GitHub shows each check on the PR page with a green checkmark or red X.
Select specific job names from your workflow:
jobs:
lint-and-typecheck:
name: Lint & Type Check # This name appears as a status check
# ...
unit-tests:
name: Unit Tests (18) # Matrix jobs get the matrix value appended
name: Unit Tests (20)
# ...
e2e-tests:
name: E2E Tests
# ...In branch protection settings, mark these as required:
- "Lint & Type Check"
- "Unit Tests (20)"
- "E2E Tests"
The merge button stays disabled until all required checks pass.
PR-Level Quality Checks
Beyond test results, you can enforce other quality standards in your pipeline:
Lint Check
Catch code style issues before review:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
- run: npx eslint . --max-warnings 0The --max-warnings 0 flag treats warnings as errors. This prevents lint warnings from accumulating over time.
Type Check
Ensure TypeScript compiles without errors:
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
- run: npx tsc --noEmitBundle Size Check
Prevent bundle size regressions:
bundle-size:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
- run: npm run build
- run: npx bundlesizeCommit Message Format
Enforce conventional commits using a GitHub Action:
commit-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
- run: npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }}Blocking Merge on Failure
When a required check fails, the PR page shows clearly what went wrong:
✓ Lint & Type Check — passed
✗ Unit Tests (20) — failed
○ E2E Tests — skipped (depends on Unit Tests)The merge button is disabled with a message: "Required status checks must pass before merging." Developers must fix the failing tests, push a new commit, and wait for the pipeline to pass.
Handling Flaky Tests
Flaky tests that randomly fail create a serious problem for quality gates. Developers lose trust and start retrying until they get green, or worse, asking to remove the check.
Strategies for handling flakiness:
# Allow retries in the test configuration
e2e-tests:
runs-on: ubuntu-latest
steps:
- run: npx playwright test --retries=2Additionally, track flaky tests over time:
// playwright.config.ts
export default defineConfig({
retries: process.env.CI ? 2 : 0,
reporter: [
['html'],
['json', { outputFile: 'test-results.json' }],
],
});Review the JSON results to identify tests that needed retries. Fix flaky tests as a priority — they erode confidence in the entire pipeline.
CODEOWNERS for Critical Paths
GitHub's CODEOWNERS file can require specific reviewers for sensitive areas:
# .github/CODEOWNERS
# CI configuration requires DevOps review
.github/workflows/ @devops-team
# Security-sensitive code requires security review
src/auth/ @security-team
# Database migrations require DBA review
migrations/ @dba-teamThis ensures domain experts review changes in their area, adding a human quality gate alongside the automated ones.
Key Takeaways
- Branch protection rules prevent merging code that fails required status checks.
- Mark your critical CI jobs (lint, type check, tests) as required status checks.
- Use
--max-warnings 0with ESLint and--noEmitwith TypeScript for strict checks. - Handle flaky tests with retries and tracking — do not remove the quality gate.
- Use CODEOWNERS to require expert review for sensitive code paths.