Last updated 2026-05-07
Matrix & Reusable Workflows
Matrix strategies
Full support: cartesian product over keys, plus include
additions and exclude removals.
strategy: matrix: node: [18, 20, 22] os: [ubuntu-latest, macos-latest] include: - node: 18 experimental: true exclude: - node: 22 os: ubuntu-latest fail-fast: true # default — cancels remaining on first failure max-parallel: 2 # at most 2 matrix instances run concurrentlyHow the runner expands this:
- Compute the cartesian product of the matrix keys.
- Remove every combination that matches any
excludeentry. - For each
include, merge into a matching combination if all overlapping keys agree, otherwise add as a new instance. - Apply
max-parallelat scheduling time. - If any instance fails and
fail-fastis true, cancel the rest.
Sibling output
jobs: test: strategy: matrix: node: [18, 20] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} - run: npm ci - run: npm testReusable workflows
Both local (./) and
remote (org/repo@ref) reusable
workflows are supported.
Calling a local reusable workflow
jobs: tests: uses: ./.github/workflows/shared-tests.yml with: environment: staging secrets: inheriton: workflow_call: inputs: environment: type: string required: true secrets: API_KEY: required: false
jobs: run-tests: runs-on: ubuntu-latest steps: - run: echo "Testing in ${{ inputs.environment }}" - run: ./scripts/test.sh env: API_KEY: ${{ secrets.API_KEY }}Calling a remote reusable workflow
jobs: external: uses: org/repo/.github/workflows/shared.yml@main with: param: value secrets: API_KEY: ${{ secrets.API_KEY }}
Remote workflows are git-cloned and cached at
~/.local/share/local-runner/workflow-cache/.
Authentication for private repos uses GITHUB_TOKEN /
GH_TOKEN from the runner environment.
Secrets handling
-
secrets: inherit— every secret in the calling workflow is forwarded to the called workflow. -
secrets: { KEY: value }— explicit forwarding, one secret at a time, evaluated through the expression engine. -
A called workflow's
workflow_call.secretsblock declares which secrets it expects.
Job dependencies
Dependencies are resolved by topological sort with cycle detection.
Failure on a dependency makes downstream jobs skip (unless they use
if: always() or if: failure()).
jobs: build: runs-on: ubuntu-latest steps: [{ run: echo build }] test: needs: build runs-on: ubuntu-latest steps: [{ run: echo test }] deploy: needs: [build, test] if: success() && github.ref == 'refs/heads/main' runs-on: ubuntu-latest steps: [{ run: ./deploy.sh }]