Skip to content
Latchkey

GitHub Actions Matrix include/exclude Produces Wrong Combinations

The matrix expands into unexpected jobs because include and exclude follow specific rules: include can either extend an existing combination or add a brand-new one, and exclude must match every key of a combination to remove it.

What this error means

You get more or fewer matrix jobs than intended - an include entry created a whole new job instead of adding a field, or an exclude removed nothing because its keys did not fully match.

.github/workflows/ci.yml
matrix:
  os: [ubuntu-latest]
  node: [18, 20]
  include:
    - node: 22          # adds a NEW job (no os), total 3 not 2
  exclude:
    - node: 18          # only matches if it pins all original keys

Diagnose it: print the context before you change anything

Most workflow-expression bugs are not syntax errors, they are an expression reading something that is empty. GitHub resolves a missing property to an empty string instead of failing the run, so a wrong reference looks like a logic bug rather than a mistake. Dump the contexts first and you will usually see the answer immediately.

.github/workflows/ci.yml
- name: Dump contexts
  run: |
    echo '--- github ---'   ; echo '${{ toJSON(github) }}'
    echo '--- needs ---'    ; echo '${{ toJSON(needs) }}'
    echo '--- steps ---'    ; echo '${{ toJSON(steps) }}'
    echo '--- matrix ---'   ; echo '${{ toJSON(matrix) }}'
    echo '--- inputs ---'   ; echo '${{ toJSON(inputs) }}'

Check the context is allowed where you used it

Contexts are not available everywhere. The same expression can be valid in a step if and invalid in a job if, which is why an expression that works in one workflow fails when moved.

Where you wrote itContexts available there
run-namegithub, inputs, vars
concurrencygithub, inputs, vars
Top-level envgithub, secrets, inputs, vars
jobs.<id>.ifgithub, needs, vars, inputs
jobs.<id>.steps.ifgithub, needs, strategy, matrix, job, runner, env, vars, steps, inputs
jobs.<id>.outputsFull access, including secrets
Reusable workflow outputsgithub, jobs, vars, inputs

Common causes

include extends vs adds depending on keys

If an include entry only sets keys already in the matrix, it expands a matching combination. If it introduces a new value not in the base, it adds a separate job.

exclude must match a full combination

exclude removes a combination only when its keys match an existing generated combination. A partial key set may not match the way you expect.

How to fix it

Be explicit about include entries

Specify all base keys in an include entry when you want to extend a specific combination, not create a new bare one.

.github/workflows/ci.yml
matrix:
  os: [ubuntu-latest]
  node: [18, 20]
  include:
    - os: ubuntu-latest
      node: 20
      coverage: true     # extends the existing 20 job

Verify the expansion

  1. Print the matrix context with a debug job to see the real combinations.
  2. Pin every relevant key in exclude entries so they match exactly.
  3. Use fail-fast: false while tuning so one leg does not cancel the rest.

Catch it before it reaches CI

Every failure in this cluster is statically detectable. actionlint parses workflow expressions, checks context availability against the same rules above, and validates needs references, so these bugs never need to cost you a run.

Terminal
# one-off
docker run --rm -v "$(pwd):/repo" --workdir /repo rhysd/actionlint:latest -color

# as a job, before anything expensive runs
- uses: actions/checkout@v4
- run: |
    bash <(curl -s https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)
    ./actionlint -color

How to prevent it

  • Treat include as add/extend and exclude as full-match removal.
  • Echo the matrix combinations when changing include/exclude.
  • Keep the base matrix small and layer specifics via include deliberately.

Frequently asked questions

What causes GitHub Actions matrix include/exclude produces wrong combinations?
There are 2 common causes: include extends vs adds depending on keys and exclude must match a full combination. If an include entry only sets keys already in the matrix, it expands a matching combination.
How do I fix GitHub Actions matrix include/exclude produces wrong combinations?
There are 2 fixes depending on which cause you have: be explicit about include entries and verify the expansion. Work through them in order, since the first is the most common.
What does GitHub Actions matrix include/exclude produces wrong combinations actually mean?
You get more or fewer matrix jobs than intended - an include entry created a whole new job instead of adding a field, or an exclude removed nothing because its keys did not fully match.
How do I stop GitHub Actions matrix include/exclude produces wrong combinations happening again?
Treat include as add/extend and exclude as full-match removal. The prevention section lists 3 changes that keep it from recurring.

Related guides

References

Not every red build is your code. Latchkey repairs the ones that are not, on the runner. Start free → 30-day trial · No credit card