A GitHub Actions workflow that doesn't block on quality failures isn't a quality pipeline — it's a deployment script with decoration. Quality gates must have teeth: they block the merge, they block the deploy, and they report clearly why.

The Gate Architecture

Design your workflow as a series of required jobs. Each job is a gate. The deploy job only runs if all gates pass.

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run lint

  unit-tests:
    needs: lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run test:unit -- --coverage

  e2e-tests:
    needs: unit-tests
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npx playwright test

  deploy:
    needs: [lint, unit-tests, e2e-tests]
    if: success()
    runs-on: ubuntu-latest
    steps:
      - run: echo "All gates passed — deploying"

Coverage Enforcement

Don't just run tests — enforce a minimum coverage threshold:

- name: Check Coverage
  run: |
    COVERAGE=$(cat coverage/coverage-summary.json | jq '.total.lines.pct')
    if (( $(echo "$COVERAGE < 80" | bc -l) )); then
      echo "Coverage $COVERAGE% is below 80% threshold"
      exit 1
    fi

Branch Protection Rules

Quality gates only work if the branch is protected. In GitHub repository settings:

Without branch protection, developers can bypass the gates with a direct push.

Parallelising for Speed

Run independent gates in parallel. Lint and unit tests can run simultaneously. E2E can shard across multiple runners.

strategy:
  matrix:
    shard: [1, 2, 3, 4]
steps:
  - run: npx playwright test --shard=${{ matrix.shard }}/4

Conclusion

GitHub Actions is powerful enough to enforce whatever quality bar your team sets. The limiting factor is never the tooling — it's the willingness to make the gates required and hold the line when they fail.