GitHub Actions needs.<job>.outputs undefined when upstream skipped
A downstream job reads needs.<job>.outputs.* only when the upstream job ran and set them. A skipped upstream, or one that never wrote GITHUB_OUTPUT, yields empty values.
What this error means
A consumer job sees an empty needs output and behaves as if the value was never set, often after the producer was skipped or did not write the output.
# needs.setup.outputs.tag is '' because 'setup' was skipped
echo "tag=${{ needs.setup.outputs.tag }}"Common causes
Upstream job skipped
A skipped producer never executes its steps, so no outputs are set.
Output not declared at job level
Job outputs must be mapped from a step output via jobs.<id>.outputs.
How to fix it
Declare outputs and guard consumption
- Map step outputs to job outputs under jobs.<id>.outputs.
- Ensure the producer actually runs (check its if).
- Default in the consumer: \${{ needs.setup.outputs.tag || 'latest' }}.
setup:
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.t.outputs.tag }}
steps:
- id: t
run: echo "tag=v1.2.3" >> "${GITHUB_OUTPUT}"How to prevent it
- Always map step outputs to job outputs explicitly.
- Default consumer expressions so a skipped producer does not break logic.