GitHub Actions Tutorial — Calling Workflows

2 minute read

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:

  1. Check the reusable workflow’s logs in the caller’s job details
  2. Verify input types match (string vs boolean vs number)
  3. Confirm secrets are passed (not inherited by default)
  4. 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 →

Updated: