How to Reuse a Workflow With workflow_call in GitHub Actions
workflow_call turns a workflow into a callable unit other workflows invoke like an action.
Define on.workflow_call with inputs and secrets in the reusable file, then reference it from a caller job via uses: owner/repo/.github/workflows/file.yml@ref.
Steps
- Add
on.workflow_callwithinputs:andsecrets:to the reusable workflow. - In the caller, set
uses:to the workflow path and@ref. - Pass values with
with:and forward secrets withsecrets:.
Caller workflow
jobs:
call-build:
uses: my-org/ci/.github/workflows/build.yml@main
with:
node-version: '20'
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}Gotchas
- Reusable workflows can nest up to four levels deep.
- Use
secrets: inheritto forward all caller secrets without listing each.
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.