GitHub Actions matrix variable shadowed by an env var of the same name
matrix.<name> lives in the matrix context and env.<name> lives in the env context. They are distinct, but if you export a matrix value into env with the same name and then read it inconsistently, a step can pick up the env value instead of the per-leg matrix value.
What this error means
Every matrix leg behaves as if it has the same value; the per-leg matrix value is effectively ignored in shell steps.
github-actions
env:
version: '18' # workflow-level env
strategy:
matrix:
version: [18, 20, 22]
steps:
- run: echo "$version" # reads env.version=18 in every legCommon causes
env and matrix keys collide by name
A shell reading $version resolves the env var, not the matrix context, so the matrix value is masked.
Implicit env precedence in run steps
run steps see the process environment; matrix values are only available via the matrix context expression.
How to fix it
Reference the matrix context explicitly
- Use the matrix expression in the workflow, not a bare shell variable, to read per-leg values.
- Remove the colliding workflow-level env entry.
.github/workflows/ci.yml
steps:
- run: echo "${{ matrix.version }}"Map the matrix value into a distinctly named env
- If you need an env var, assign it from the matrix context under a unique name.
- Avoid reusing the matrix key name for the env var.
step env
env:
NODE_VERSION: ${{ matrix.version }}How to prevent it
- Never reuse a matrix key name for a workflow- or job-level env var.
- Prefer matrix.<name> expressions over bare shell variables for per-leg values.
Related guides
GitHub Actions env at Workflow vs Job vs Step - Wrong Value WinsFix GitHub Actions env variable scoping - workflow, job, and step env blocks override each other, and step-le…
GitHub Actions Environment Variable Not Expanding in StepsFix GitHub Actions env vars that come through empty or literal - mixing ${{ }} and shell $VAR syntax, or sett…
GitHub Actions matrix fail-fast cancels healthy sibling jobs unexpectedlyFix matrix jobs that get cancelled the moment one matrix leg fails because strategy.fail-fast defaults to tru…