Test matrix
Fan one test command across N shards via the `matrix-fanout` run — Workflows `createBatch` spawns the children, scale-to-zero between runs.
Fires on Every PR push — wire it any of these ways:
- Action recommended A ci.yml step dispatches the run — when it must interleave with other CI jobs or gate the PR.
- Webhook pattern The GitHub App webhook fires the run directly — no .github/workflows file, no GHA minutes. (needs command + shards from config)
- Use case
- Same command fanned out across N shards
- FlareDispatch shape
- 1 typed run + dispatch
- Plain GHA shape
- 1 workflow · matrix + aggregate job
Source
The recommended FlareDispatch shape is shown first. Toggle to Without FlareDispatch to see the full GitHub Actions workflow a team would maintain to do the same job without it.
Action mode — triggered by a GitHub Actions workflow that calls the shipped run.
# Recipe: sharded test matrix on Cloudflare
#
# Use case: a unit/integration suite that is fast per-shard but long in total.
# Offload it to the shipped `matrix-fanout` run, which spawns N child
# Workflows (one container per shard) and reports a single parent check-run
# that is green only if every shard passes.
#
# Mode: Action mode, fire-and-forget. See specs/04-gha-integration.md.
# Run: matrix-fanout — see specs/02-runs.md#2-matrix-fanout.
name: test-matrix
on:
pull_request:
jobs:
test-matrix:
runs-on: ubuntu-latest
steps:
- uses: openhackersclub/flare-dispatch-action@v1
with:
run: matrix-fanout
endpoint: ${{ vars.FLAREDISPATCH_ENDPOINT }}
hmac-secret: ${{ secrets.FLAREDISPATCH_HMAC }}
inputs: |
{
"repo": "${{ github.repository }}",
"sha": "${{ github.sha }}",
"command": "pnpm test --shard $SHARD_INDEX/$SHARD_TOTAL",
"shards": 8,
"failureBehavior": "wait-all"
}
# Each shard receives SHARD_INDEX / SHARD_TOTAL in its environment.
# Require the check-run `flare-dispatch/matrix-fanout` in branch protection. // Recipe: sharded test matrix — the `matrix-fanout` Run
//
// Re-exports the canonical run from runs/matrix-fanout.ts (the Dispatcher's
// registered implementation). Copy the runs/matrix-fanout.ts file verbatim
// into your own repo when forking this recipe — the indirection here keeps
// the recipe page in sync with the registered run without duplicating code.
//
// Spec: specs/02-runs.md § 2. DSL: specs/03-dsl.md.
export { matrixFanout } from "@flare-dispatch/runs/matrix-fanout";
A faithful, runnable GHA workflow — what a team actually
maintains to do this job without FlareDispatch.
# Recipe: sharded test matrix — BASELINE (without FlareDispatch)
#
# This is what you'd maintain on plain GitHub Actions to fan one test command
# across N shards with proper failure propagation and log artifacts. Shown
# here ONLY as a comparison — see ./matrix-fanout.run.ts for the
# FlareDispatch version (~50 lines, one primitive: `sharded`).
#
# Notable costs of the GHA-only path:
# - The shard count is hardcoded in two places (the `matrix:` block and the
# `--shard $i/$N` divisor). Bump it from 4 to 8 and you must remember to
# edit both — there's no DRY here.
# - Every shard pays the GHA runner cold-start tax (~20–30 s) AND the
# `pnpm install` cost. Worker-pool caching (per-shard `actions/cache`)
# helps but never gets to "warm container in <2 s" the way the
# `flare-dispatch` worker pool does.
# - `fail-fast: false` is necessary so a single failing shard doesn't
# cancel the rest — but then you have to ADD a separate `aggregate` job
# to actually propagate that failure to the PR check.
# - Per-shard logs go through `actions/upload-artifact` blob storage and
# can only be viewed by clicking into the Artifacts UI. FlareDispatch
# surfaces them as signed R2 URLs in the check-run summary directly.
name: test-matrix-baseline
on:
pull_request:
jobs:
shard:
name: shard ${{ matrix.shard }}/8
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4, 5, 6, 7, 8]
timeout-minutes: 20
env:
SHARD_INDEX: ${{ matrix.shard }}
SHARD_TOTAL: 8
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run tests (shard ${{ matrix.shard }}/8)
# CONFIGURE: your test command. Must support shard splitting via the
# env vars above (vitest, jest, mocha-parallel all have flavours).
run: pnpm test --shard $SHARD_INDEX/$SHARD_TOTAL | tee shard-${{ matrix.shard }}.log
- name: Upload shard log
if: always()
uses: actions/upload-artifact@v4
with:
name: shard-${{ matrix.shard }}-log
path: shard-${{ matrix.shard }}.log
retention-days: 7
# GHA quirk: `fail-fast: false` keeps shards running independently — but
# the job-level check is then green even if shards failed. This aggregate
# job is what branch protection must require, NOT `shard`.
aggregate:
name: test-matrix
if: always()
needs: [shard]
runs-on: ubuntu-latest
steps:
- name: Propagate shard failures
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
run: |
echo "::error::One or more shards failed."
exit 1