Skip to content

Fix: GitHub Actions Reusable Workflow Not Working — Inputs Not Passed or Secrets Not Available

FixDevs ·

Quick Answer

How to fix GitHub Actions reusable workflow issues — workflow_call trigger, passing inputs and secrets, output variables, caller vs called permissions, and common errors.

The Problem

A reusable workflow doesn’t receive inputs from the caller:

# caller.yml
jobs:
  deploy:
    uses: ./.github/workflows/deploy.yml
    with:
      environment: production  # Input not available in called workflow

Or secrets aren’t passed to the reusable workflow:

# In called workflow — secret is empty
- run: echo "Token is ${{ secrets.API_TOKEN }}"
# Output: Token is

Or the called workflow can’t access outputs from the reusable workflow:

# Caller — output is empty
- run: echo "Version is ${{ needs.build.outputs.version }}"
# Output: Version is

Or the workflow fails with:

Error: Unrecognized named-value: 'inputs'
Error: Required input 'environment' is not provided

Why This Happens

Reusable workflows use the workflow_call trigger and have strict rules for passing data:

  • workflow_call trigger required — the called workflow must declare on: workflow_call: to accept calls from other workflows. Without it, the workflow can’t be reused.
  • Inputs must be declared — the called workflow must explicitly declare every input under on.workflow_call.inputs. Passing undeclared inputs is silently ignored.
  • Secrets must be passed explicitly — secrets from the caller are not automatically available in called workflows. They must be explicitly passed via secrets: in the caller and declared in the called workflow. Or use secrets: inherit to pass all secrets automatically.
  • inputs context only available in called workflows — using ${{ inputs.foo }} in a regular workflow (not called via workflow_call) causes “Unrecognized named-value: ‘inputs’”.

Fix 1: Define workflow_call Correctly

The called workflow must declare on: workflow_call: with all inputs and secrets:

# .github/workflows/deploy.yml — REUSABLE workflow
name: Deploy

on:
  workflow_call:
    # Declare all inputs this workflow accepts
    inputs:
      environment:
        required: true
        type: string
        description: 'Target environment (staging, production)'
      docker_tag:
        required: false
        type: string
        default: 'latest'
        description: 'Docker image tag to deploy'
      dry_run:
        required: false
        type: boolean
        default: false

    # Declare secrets this workflow needs
    secrets:
      DEPLOY_TOKEN:
        required: true
        description: 'Token for deployment API'
      SLACK_WEBHOOK:
        required: false

    # Declare outputs this workflow produces
    outputs:
      deployment_id:
        description: 'ID of the created deployment'
        value: ${{ jobs.deploy.outputs.deployment_id }}

jobs:
  deploy:
    runs-on: ubuntu-latest
    outputs:
      deployment_id: ${{ steps.create-deployment.outputs.deployment_id }}

    steps:
      - name: Deploy to ${{ inputs.environment }}
        id: create-deployment
        if: ${{ !inputs.dry_run }}
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
        run: |
          DEPLOYMENT_ID=$(./scripts/deploy.sh \
            --env ${{ inputs.environment }} \
            --tag ${{ inputs.docker_tag }})
          echo "deployment_id=$DEPLOYMENT_ID" >> $GITHUB_OUTPUT

      - name: Dry run notification
        if: ${{ inputs.dry_run }}
        run: echo "DRY RUN: would deploy ${{ inputs.docker_tag }} to ${{ inputs.environment }}"

      - name: Notify Slack
        if: ${{ secrets.SLACK_WEBHOOK != '' }}
        run: |
          curl -X POST ${{ secrets.SLACK_WEBHOOK }} \
            -d '{"text": "Deployed to ${{ inputs.environment }}"}'

Fix 2: Call a Reusable Workflow with Inputs and Secrets

# .github/workflows/release.yml — CALLER workflow
name: Release

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      docker_tag: ${{ steps.tag.outputs.tag }}
    steps:
      - uses: actions/checkout@v4
      - name: Generate tag
        id: tag
        run: echo "tag=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT

  deploy-staging:
    needs: build
    uses: ./.github/workflows/deploy.yml  # Relative path for same repo
    with:
      environment: staging
      docker_tag: ${{ needs.build.outputs.docker_tag }}
    secrets:
      DEPLOY_TOKEN: ${{ secrets.STAGING_DEPLOY_TOKEN }}
      # SLACK_WEBHOOK: not passed — optional secret, caller omits it

  deploy-production:
    needs: [build, deploy-staging]
    uses: ./.github/workflows/deploy.yml
    with:
      environment: production
      docker_tag: ${{ needs.build.outputs.docker_tag }}
    secrets:
      DEPLOY_TOKEN: ${{ secrets.PROD_DEPLOY_TOKEN }}
      SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

  # Use outputs from the reusable workflow
  post-deploy:
    needs: deploy-production
    runs-on: ubuntu-latest
    steps:
      - name: Log deployment ID
        run: echo "Deployed with ID ${{ needs.deploy-production.outputs.deployment_id }}"

Using secrets: inherit (simpler):

# Pass ALL secrets from caller to called workflow automatically
deploy-production:
  uses: ./.github/workflows/deploy.yml
  with:
    environment: production
  secrets: inherit  # All secrets from the caller are available

Note: secrets: inherit passes all secrets the caller has access to. The called workflow must still declare which secrets it uses under on.workflow_call.secrets — but they don’t need required: true to be available.

Fix 3: Reference Reusable Workflows in Other Repos

Call workflows from other repositories:

# Call a workflow from another repository
jobs:
  lint:
    uses: myorg/shared-workflows/.github/workflows/lint.yml@main
    # Format: {owner}/{repo}/.github/workflows/{file}@{ref}
    with:
      node_version: '20'
    secrets:
      NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

  # Use a specific commit SHA for reproducible builds
  deploy:
    uses: myorg/shared-workflows/.github/workflows/deploy.yml@abc1234
    with:
      environment: production

  # Use a release tag
  test:
    uses: myorg/shared-workflows/.github/workflows/[email protected]

Calling across organizations requires explicit permissions:

# The called workflow's repository must allow it
# Settings → Actions → General → Allow {org} to use reusable workflows
# Or use a PAT with repo scope as a secret

Fix 4: Pass Matrix Values to Reusable Workflows

Combine matrix strategy with reusable workflows:

# Caller — matrix of environments
jobs:
  deploy-matrix:
    strategy:
      matrix:
        environment: [dev, staging, production]
        include:
          - environment: production
            requires_approval: true
          - environment: staging
            requires_approval: false
          - environment: dev
            requires_approval: false

    uses: ./.github/workflows/deploy.yml
    with:
      environment: ${{ matrix.environment }}
    secrets: inherit

Note: You can’t pass matrix values directly to the uses: path — only to with: and secrets:. The workflow path itself must be a static string.

Fix 5: Reusable Workflow Patterns

Build-once, deploy-many pattern:

# .github/workflows/ci-cd.yml
jobs:
  test:
    uses: ./.github/workflows/test.yml
    secrets: inherit

  build:
    needs: test
    uses: ./.github/workflows/build.yml
    with:
      push_image: true
    secrets: inherit
    # outputs: image_tag

  deploy-staging:
    needs: build
    uses: ./.github/workflows/deploy.yml
    with:
      environment: staging
      image_tag: ${{ needs.build.outputs.image_tag }}
    secrets: inherit

  integration-tests:
    needs: deploy-staging
    uses: ./.github/workflows/integration-test.yml
    with:
      target_url: https://staging.example.com
    secrets: inherit

  deploy-production:
    needs: [build, integration-tests]
    uses: ./.github/workflows/deploy.yml
    with:
      environment: production
      image_tag: ${{ needs.build.outputs.image_tag }}
    secrets: inherit

Adding environment protection rules:

# deploy.yml — require approval for production
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}  # Links to GitHub Environment settings
    # production environment has protection rules (required reviewers)
    steps:
      - name: Deploy
        run: ./deploy.sh

Fix 6: Debug Reusable Workflow Issues

# Add debug output to trace inputs and secrets
jobs:
  debug:
    runs-on: ubuntu-latest
    steps:
      - name: Print inputs
        run: |
          echo "environment: ${{ inputs.environment }}"
          echo "docker_tag: ${{ inputs.docker_tag }}"
          echo "dry_run: ${{ inputs.dry_run }}"

      - name: Check secret availability
        run: |
          if [ -z "$SECRET_VALUE" ]; then
            echo "DEPLOY_TOKEN is empty!"
          else
            echo "DEPLOY_TOKEN is set (length: ${#SECRET_VALUE})"
          fi
        env:
          SECRET_VALUE: ${{ secrets.DEPLOY_TOKEN }}

Common error messages and fixes:

Error: "Unrecognized named-value: 'inputs'"
→ The workflow isn't called via workflow_call — inputs context unavailable
→ Add 'on: workflow_call:' or check the trigger type

Error: "Required input 'environment' is not provided"
→ Caller uses `with:` but doesn't include all required inputs
→ Add the missing input in the caller's `with:` block

Error: "Unexpected value 'uses'"
→ 'uses' at job level requires no 'steps' — jobs with 'uses' can't have steps
→ Split into two separate jobs

Warning: "Secret 'API_TOKEN' was not passed to the called workflow"
→ The called workflow declares the secret but caller doesn't pass it
→ Add 'secrets: { API_TOKEN: ${{ secrets.API_TOKEN }} }' to the caller

Still Not Working?

Reusable workflow job can’t have steps — a job that calls a reusable workflow with uses: cannot also have steps:. If you need both, use two separate jobs with needs:.

env context not available in called workflows — environment variables set in the caller are not passed to called workflows. Pass them as explicit inputs instead.

Outputs from reusable workflows need needs: reference — to use outputs from a reusable workflow job, the consumer job must declare it in needs: and reference it as ${{ needs.<job-id>.outputs.<output-name> }}. The output must be declared both at the step level (echo "foo=bar" >> $GITHUB_OUTPUT), job level (outputs: foo: ${{ steps.step-id.outputs.foo }}), and workflow_call level (outputs: foo: value: ${{ jobs.job-id.outputs.foo }}).

For related GitHub Actions issues, see Fix: GitHub Actions Cache Not Working and Fix: GitHub Actions Timeout.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles