GitHub Actions Dynamic Matrix from fromJSON Fails - Not an Array
A dynamic matrix fed by fromJSON of an upstream output fails because the output was empty, not valid JSON, or not shaped as an array the matrix expander accepts.
What this error means
The dependent job fails to expand its matrix, or produces zero jobs, when the JSON it consumes is empty or malformed. The producer job often succeeded, hiding the real cause.
Error: The template is not valid. .github/workflows/ci.yml:
matrix value 'fromJSON(needs.gen.outputs.list)' did not evaluate to an arrayDiagnose 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.
- 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 it | Contexts available there |
|---|---|
run-name | github, inputs, vars |
concurrency | github, inputs, vars |
Top-level env | github, secrets, inputs, vars |
jobs.<id>.if | github, needs, vars, inputs |
jobs.<id>.steps.if | github, needs, strategy, matrix, job, runner, env, vars, steps, inputs |
jobs.<id>.outputs | Full access, including secrets |
Reusable workflow outputs | github, jobs, vars, inputs |
Common causes
Upstream output empty or truncated
If the producer job did not set the output (skipped step, wrong id, or size truncation), fromJSON receives an empty string and cannot produce an array.
JSON not an array of values
A matrix dimension needs a JSON array. A bare object, a quoted string, or trailing-comma JSON makes fromJSON fail or produce a non-array.
How to fix it
Emit valid JSON array as a job output
jobs:
gen:
runs-on: ubuntu-latest
outputs:
list: ${{ steps.set.outputs.list }}
steps:
- id: set
run: echo 'list=["18","20","22"]' >> "$GITHUB_OUTPUT"
test:
needs: gen
strategy:
matrix:
node: ${{ fromJSON(needs.gen.outputs.list) }}
runs-on: ubuntu-latestValidate the JSON before consuming it
- Echo the producer output and confirm it is a non-empty JSON array.
- Use a tool like jq -c to compact and validate the JSON you emit.
- Guard the dependent job so it does not run on an empty list.
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.
# 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 -colorHow to prevent it
- Always emit compact, valid JSON arrays for dynamic matrices.
- Map the producer step output to a job output and confirm it is non-empty.
- Validate the JSON shape (array of scalars or objects) before fromJSON.