Triggers, concurrency and matrix builds

Event and path filters, scheduled and manual runs, concurrency groups that cancel stale work, matrix expansion, fail-fast, and job dependencies with needs.

Choosing what starts a pipeline

name: ci
on:
  push:
    branches: [main]
    paths-ignore: ['docs/**', '*.md']

  pull_request:
    types: [opened, synchronize, reopened]
    paths: ['src/**', 'package.json', 'package-lock.json']

  workflow_dispatch:
    inputs:
      environment:
        description: Target environment
        type: choice
        options: [staging, production]
        required: true

  schedule:
    - cron: '0 3 * * *'        # nightly full suite in UTC

# cancel a running job when a newer commit arrives on the same branch
concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true
  • paths filters prevent a docs-only change from running the whole test suite, which is the easiest pipeline speed-up available.
  • pull_request runs against a merge commit; pull_request_target runs in the base repository context with secrets — never check out untrusted code in that mode.
  • A schedule always runs on the default branch and uses UTC, so a nightly job at 03:00 UTC drifts against local time when daylight saving changes.
  • cancel-in-progress on a per-branch group stops a superseded run and frees a runner immediately.

Matrix builds

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      max-parallel: 4
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        node: [20, 22]
        exclude:
          - os: windows-latest
            node: 20
        include:
          - os: ubuntu-latest
            node: 22
            coverage: true
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
          cache: npm
      - run: npm ci
      - run: npm test
      - if: matrix.coverage
        run: npm run test:coverage
SettingEffectWhen to use
fail-fast: falseKeep other cells running after one failsWhen you need the full failure picture
max-parallelCap concurrent cellsShared infrastructure with a limit
excludeRemove unsupported combinationsAn OS that may not be paired with a version
includeAdd one extra cell or extra keysA single special case
Dynamic matrixA job outputs the matrix JSONBuild only the projects that changed

A matrix grows multiplicatively. Three operating systems, four language versions and two architectures is twenty-four cells, each consuming a runner and paid minutes — trim it to the combinations a user actually has.

Dependencies between jobs

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.meta.outputs.version }}
    steps:
      - uses: actions/checkout@v4
      - id: meta
        run: echo "version=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT"
      - run: ./build.sh
      - uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/

  integration:
    needs: build                       # starts only after build succeeds
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v4
        with: { name: dist }
      - run: ./integration.sh

  deploy:
    needs: [build, integration]
    if: github.ref == 'refs/heads/main' && needs.build.result == 'success'
    runs-on: ubuntu-latest
    environment: production            # gates on required reviewers
    steps:
      - run: ./deploy.sh --version ${{ needs.build.outputs.version }}
⚠️
A skipped or cancelled dependency propagates: a job with needs does not run when its parent is skipped, unless you use if: always(). Use that deliberately for reporting jobs, not to bypass a real gate.

FAQ

Should I cancel in-progress runs on a pull request?
Yes for the test pipeline, so a new commit stops wasting a runner on the old one. Do not cancel a deployment pipeline mid-flight — a partially applied release is worse than a slow one.
How do I avoid running the full matrix on every commit?
Run the current runtime versions on every pull request and the full matrix on the default branch or nightly. You keep fast feedback and still catch version-specific problems before release.

CI/CD concepts and choosing a platform Testing strategy inside the pipeline

Last refreshed 2026-09-18.