How to Set a Matrix From Previous Job Output in GitHub Actions
When the set of things to test is computed at runtime, the matrix has to come from a previous jobs output.
Emit a JSON array to ${GITHUB_OUTPUT} in a setup job, expose it as a job output, then feed it to the matrix with fromJSON.
Steps
- In a setup job, build the JSON array (e.g. from changed files).
- Write
matrix=<json>to${GITHUB_OUTPUT}and map it to a job output. - In the downstream job add
needs: setup. - Set
matrix: ${{ fromJSON(needs.setup.outputs.matrix) }}.
Workflow
jobs:
setup:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.gen.outputs.matrix }}
steps:
- id: gen
run: echo "matrix=[\"api\",\"web\",\"worker\"]" >> "${GITHUB_OUTPUT}"
test:
needs: setup
runs-on: ubuntu-latest
strategy:
matrix:
service: ${{ fromJSON(needs.setup.outputs.matrix) }}
steps:
- run: ./test.sh ${{ matrix.service }}Gotchas
- The output must be valid JSON; a stray trailing comma fails
fromJSONsilently as an empty matrix. - An empty array produces zero matrix jobs, which can look like a skipped step.
- Latchkey scales these dynamic matrix legs onto cheaper runners and retries any leg that fails transiently.
Verify it actually works
A workflow that runs is not a workflow that works. Confirm the behaviour on a real event rather than on a manual dispatch, because trigger conditions, permissions, and context values all differ between the two.
# 1. validate the file before pushing
docker run --rm -v "$(pwd):/repo" --workdir /repo rhysd/actionlint:latest -color
# 2. trigger the real event, not workflow_dispatch
git commit --allow-empty -m "ci: verify trigger" && git push
# 3. watch it and read the conclusion, not just the colour
gh run watch
gh run view --log-failedWhat usually goes wrong first
- The workflow file must exist on the default branch before scheduled or dispatch triggers appear at all.
GITHUB_TOKENpermissions default to read-only in many organisations. Declare apermissions:block listing every scope the job needs.- Fork pull requests get a read-only token and no access to secrets, regardless of workflow configuration.
actions/checkoutgives you depth 1 on a detached HEAD, so anything needing history or a branch name needsfetch-depth: 0.