GitHub Actions Tutorial — Calling Workflows
Calling Workflows
This page covers the uses: syntax for calling reusable workflows, version pinning strategies, and how caller and callee interact.
Calling Syntax
Same Repository
jobs:
build:
uses: ./.github/workflows/build.yml
with:
node-version: '20'
The path is relative to the repository root. The file must be in .github/workflows/.
Cross-Repository
jobs:
build:
uses: my-org/shared-workflows/.github/workflows/build.yml@v2
Format: owner/repo/path@ref
Reference Types
| Ref | Example | Trade-off |
|---|---|---|
| Branch | @main |
Always latest; may break unexpectedly |
| Tag | @v2 |
Stable; update by bumping tag |
| SHA | @a1b2c3d... |
Most secure; immune to tag mutation |
Recommendation: Use tags for convenience, SHAs for security-critical workflows.
Caller Job Constraints
A caller job that uses uses: cannot define its own steps::
jobs:
# ✅ Valid — uses only
build:
uses: ./.github/workflows/build.yml
# ✅ Valid — steps only
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- run: echo "Deploying..."
# ❌ Invalid — cannot mix uses and steps
broken:
uses: ./.github/workflows/build.yml
steps:
- run: echo "This is not allowed"
Passing Context to Reusable Workflows
With Inputs and Secrets
jobs:
deploy:
uses: ./.github/workflows/deploy.yml
with:
environment: production
version: ${{ github.sha }}
secrets:
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
With Strategy Matrix
You can combine matrix with reusable workflows:
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
uses: ./.github/workflows/test.yml
with:
runner: ${{ matrix.os }}
The reusable workflow is called once per matrix combination.
Permissions
Caller permissions flow to the reusable workflow. You can also set permissions on the caller job:
jobs:
deploy:
uses: ./.github/workflows/deploy.yml
permissions:
contents: read
id-token: write # needed for OIDC
with:
environment: production
secrets: inherit
Nesting Reusable Workflows
Reusable workflows can call other reusable workflows (up to 4 levels):
ci.yml (caller)
└── test.yml (reusable, level 1)
└── lint.yml (reusable, level 2)
└── format.yml (reusable, level 3)
└── check.yml (reusable, level 4) ← max depth
Keep nesting shallow. Deep nesting makes debugging harder.
Debugging Caller-Callee Issues
When a reusable workflow fails, the caller’s job shows the error. To debug:
- Check the reusable workflow’s logs in the caller’s job details
- Verify input types match (string vs boolean vs number)
- Confirm secrets are passed (not inherited by default)
- Check the
@ref— is the correct version being used?
Common Mistakes
| Mistake | Fix |
|---|---|
steps: in a uses: job |
A job with uses: cannot have steps: |
| Reusable workflow not found | Check path, ref, and repository visibility |
| Secret not available | Pass explicitly or use secrets: inherit |
| Permissions too restrictive | Set permissions: on the caller job |
Quick Reference
| Concept | Syntax |
|---|---|
| Same repo | uses: ./.github/workflows/file.yml |
| Cross-repo | uses: owner/repo/.github/workflows/file.yml@ref |
| Inputs | with: key: value |
| Secrets | secrets: key: value or secrets: inherit |
| Permissions | permissions: on the caller job |
| Max nesting | 4 levels |
← Previous: Inputs, Outputs, Secrets
Next: Composition Patterns →