How Many Jobs Can a Matrix Fan Out To?
A single matrix generates at most 256 jobs per workflow run. Before that cap binds, runner capacity behind runs-on and billed minutes set the usable width.
Last verified:
Answer
A single matrix generates at most 256 jobs per workflow run, and a definition whose product lands above that fails the run before any job starts (GitHub Actions usage limits, checked on 2026-08-13). Two other numbers usually bind first: how many matching runners are available behind your runs-on label, and what the fan-out bills in runner minutes.
The generated count is the product of every list under strategy.matrix, minus the combinations removed by exclude, plus the include entries that match no existing combination (workflow syntax for strategy.matrix, checked on 2026-08-13). Worked through one definition, step by step:
| Definition step | Arithmetic | Jobs generated |
|---|---|---|
os with 3 values, node with 3 values | 3 x 3 | 9 |
add shard with 8 values | 3 x 3 x 8 | 72 |
exclude the macOS plus Node 20 pair | 72 - 8 | 64 |
include one entry matching no combination | 64 + 1 | 65 |
max-parallel moves none of those numbers. It caps how many of the generated jobs run at the same time, and the run still generates the full product (workflow syntax for max-parallel, checked on 2026-08-13). A 300 cell matrix with max-parallel: 10 fails the same way a 300 cell matrix without it does.
Detail
What a wide matrix bills
Fan-out buys wall clock. The billed minutes track the work, so the cost column grows with the width while the wall clock column stays flat as long as every cell can start.
Assumptions, stated up front: one dimension of shards, each cell taking 8 minutes, all cells on warp-ubuntu-latest-x64-4x at $0.008 per minute from the cloud runners documentation, no retries, and enough capacity for every cell to start at once. The fourth column prices the same fan-out at GitHub's published rate for the 4-core Linux larger runner, $0.012 per minute (GitHub Actions minute multipliers, checked on 2026-08-13).
| Matrix width | Job-minutes per run | Cost on warp-ubuntu-latest-x64-4x | Cost on the 4-core Linux larger runner | Wall clock per run |
|---|---|---|---|---|
| 32 | 32 x 8 = 256 | 256 x $0.008 = $2.048 | 256 x $0.012 = $3.072 | 8 min |
| 64 | 64 x 8 = 512 | 512 x $0.008 = $4.096 | 512 x $0.012 = $6.144 | 8 min |
| 128 | 128 x 8 = 1,024 | 1,024 x $0.008 = $8.192 | 1,024 x $0.012 = $12.288 | 8 min |
| 256 | 256 x 8 = 2,048 | 2,048 x $0.008 = $16.384 | 2,048 x $0.012 = $24.576 | 8 min |
Both machines carry the same shape: warp-ubuntu-latest-x64-4x (4 vCPU, 16 GB) costs $0.008 per minute against $0.012 per minute for the 4-core Linux larger runner (4 vCPU, 16 GB), which is 33 percent lower list price, arithmetic (0.012 - 0.008) / 0.012. GitHub list price checked on 2026-08-13.
Run the 128 cell version 200 times a month and it bills 204,800 job-minutes, so $1,638.40 against $2,457.60 at the GitHub rate, a difference of $819.20 for the same grid. Per-minute rates for every size are on the pricing page.
The sizes those Linux x64 labels resolve to, from the same documentation, checked on 2026-08-13:
| runs-on label | vCPU | RAM | Storage | Rate per minute |
|---|---|---|---|---|
| warp-ubuntu-latest-x64-2x | 2 | 8GB | 150GB SSD | $0.004 |
| warp-ubuntu-latest-x64-4x | 4 | 16GB | 150GB SSD | $0.008 |
| warp-ubuntu-latest-x64-8x | 8 | 32GB | 150GB SSD | $0.016 |
| warp-ubuntu-latest-x64-16x | 16 | 64GB | 150GB SSD | $0.032 |
Compute the width instead of hardcoding it
A hardcoded shard list drifts from the suite it splits. Once the test count grows, half the cells finish in seconds and the other half carry the run. Emitting the width from a job output keeps the two in step and keeps the product under the cap by construction.
The discover job below counts spec files, clamps the width to 64, and writes a JSON array to its outputs. The test job reads that array through fromJSON, so the matrix width is whatever the repository needs today (expressions reference for fromJSON, checked on 2026-08-13).
name: test-matrix
on:
pull_request:
jobs:
discover:
runs-on: warp-ubuntu-latest-x64-2x
outputs:
shards: ${{ steps.plan.outputs.shards }}
width: ${{ steps.plan.outputs.width }}
steps:
- uses: actions/checkout@v4
- id: plan
run: |
count=$(find tests -name '*.spec.ts' | wc -l)
width=$(( count < 64 ? count : 64 ))
if [ "$width" -lt 1 ]; then width=1; fi
shards=$(seq 1 "$width" | jq -Rsc 'split("\n") | map(select(length > 0) | tonumber)')
echo "width=$width" >> "$GITHUB_OUTPUT"
echo "shards=$shards" >> "$GITHUB_OUTPUT"
test:
needs: discover
strategy:
fail-fast: false
matrix:
shard: ${{ fromJSON(needs.discover.outputs.shards) }}
runs-on: warp-ubuntu-latest-x64-4x
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- run: npm test -- --shard=${{ matrix.shard }}/${{ needs.discover.outputs.width }}Three properties of that file matter for the ceiling. The clamp at 64 keeps the product below 256 even when the suite triples. fail-fast: false keeps the other cells running when one shard fails, so a wide grid returns a full failure list per run. Adding a second dimension multiplies against the computed width, so a two-architecture version of the same matrix generates up to 128 jobs and stays inside the cap.
The capacity side of the ceiling
The cap is GitHub's. The capacity is your runner provider's. (cloud runners documentation). Run as many jobs as your workflows need. Generally available Linux and Windows runners do not have plan-level concurrency caps. That scope covers generally available features on Linux and Windows runners; for high concurrency on macOS runners, contact [email protected].
Check the position rather than taking it on faith. The reports documentation covers the Queue Timings report, which gives queue wait at P75 and P90 per runner label and stack, plus run count, a daily average chart, and CSV export. Widen the matrix from 32 cells to 128, then read the same label a week later. A P90 that holds steady means the extra cells started on time and the wall clock column above is real. A P90 that climbs with the width means the cells are waiting on capacity, and the wall clock saving you priced is going to the queue instead. The guide to GitHub Actions concurrency limits maps each reading to the ceiling responsible for it.
Related Questions
What is the maximum number of jobs a GitHub Actions matrix can create?
A matrix generates at most 256 jobs per workflow run, checked on 2026-08-13 against GitHub's usage limits reference. The count is the product of every list under strategy.matrix, minus combinations removed by exclude, plus include entries that match no existing combination. A definition whose result is larger fails the run before any job starts. The build matrix glossary entry covers the semantics of each key, and the guide to fixing a GitHub Actions matrix explosion covers the edits that bring a product back under the cap.
Does max-parallel change the 256 job cap?
No. max-parallel caps how many of the generated jobs run at the same time, and the generated count stays the product of the matrix dimensions. Reach for it when something outside the runner fleet sets the ceiling, such as a rate limited API or a shared test database. When the product itself is over 256, split the suite across workflows or collapse a dimension, as covered in the guide to building a GitHub Actions matrix that scales.
How do I tell whether a wide matrix is really running in parallel?
Read queue wait per runner label in the Queue Timings report, which gives P75 and P90 per label and stack with CSV export (reports documentation). A 128 cell matrix whose P90 climbs with the width is waiting on capacity for that label. A flat P90 across widths means every cell started on time and job duration sets the wall clock. The guide to GitHub Actions concurrency limits walks through the three ceilings that produce each reading.
Price your own fan-out against the per-minute rates on the pricing page, then confirm the width with the Queue Timings numbers in the reports documentation.
Start with $10 in free credits
Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.