Skip to content
Latchkey

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.

.github/workflows/ci.yml
# 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 run

Common 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.

.github/workflows/ci.yml
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

  1. Pick workflow_run when the second workflow must run with different permissions or after an untrusted run completes.
  2. Pass data between separate runs via artifacts or the workflow_run payload, not needs.outputs.
  3. 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.

Related guides

Tired of flaky CI? Latchkey auto-heals failed jobs and retries them for you. Start free →