GitHub Actions workflow_call vs workflow_run - Choosing the Right Chaining
Workflows do not chain as expected because workflow_call and workflow_run are different mechanisms. workflow_call invokes a reusable workflow inline (one run, shared context); workflow_run starts a separate workflow after another completes (two runs, no shared context).
What this error means
You expect outputs or secrets to flow between two workflows but they are empty, or you wired up the wrong trigger and the second workflow either runs as a separate disconnected run or does not run at all.
# WRONG: trying to read a called workflow's job in a separate run
on:
workflow_run:
workflows: ["Reusable Build"]
jobs:
use:
steps:
- run: echo "${{ needs.build.outputs.version }}" # empty - different runCommon causes
Treating workflow_run like an inline call
workflow_run starts a brand-new run with no needs:, no shared outputs, and no shared secrets from the upstream run. It only receives the workflow_run event payload.
Treating workflow_call like an event
A reusable workflow (on: workflow_call) is invoked by a job uses: in the same run. It does not start on its own from an event and cannot be triggered by workflow_run.
How to fix it
Use workflow_call for inline reuse with outputs
When you need inputs, outputs, and secrets to flow synchronously, call a reusable workflow from a job.
jobs:
build:
uses: ./.github/workflows/reusable-build.yml
with: { env: staging }
deploy:
needs: build
uses: ./.github/workflows/deploy.yml
with: { artifact: ${{ needs.build.outputs.artifact }} }Use workflow_run only for async fan-out
- Pick workflow_run when the second workflow must run with different permissions or after an untrusted run completes.
- Pass data between separate runs via artifacts or the workflow_run payload, not needs.outputs.
- Use workflow_call when everything belongs in one logical pipeline.
How to prevent it
- Decide upfront: synchronous reuse (workflow_call) vs asynchronous trigger (workflow_run).
- Pass cross-run data through artifacts or the event payload, never needs across runs.
- Keep reusable workflows on: workflow_call and trigger pipelines on: workflow_run.