Skip to content
Latchkey

GitHub Actions Reusable Workflow "secret not defined" - Declare in workflow_call

A caller passes a secret to a reusable workflow that does not declare it under workflow_call.secrets, so the call is rejected - or the called workflow reads a secret that was never forwarded.

What this error means

The caller fails validation saying the secret is not defined in the called workflow, or the reusable workflow sees an empty secret because the caller did not pass it and did not use inherit.

Actions annotation
Invalid workflow file: secret 'DEPLOY_TOKEN' is not defined in the
referenced workflow .github/workflows/deploy.yml

Diagnose it: print the context before you change anything

Most workflow-expression bugs are not syntax errors, they are an expression reading something that is empty. GitHub resolves a missing property to an empty string instead of failing the run, so a wrong reference looks like a logic bug rather than a mistake. Dump the contexts first and you will usually see the answer immediately.

.github/workflows/ci.yml
- name: Dump contexts
  run: |
    echo '--- github ---'   ; echo '${{ toJSON(github) }}'
    echo '--- needs ---'    ; echo '${{ toJSON(needs) }}'
    echo '--- steps ---'    ; echo '${{ toJSON(steps) }}'
    echo '--- matrix ---'   ; echo '${{ toJSON(matrix) }}'
    echo '--- inputs ---'   ; echo '${{ toJSON(inputs) }}'

Check the context is allowed where you used it

Contexts are not available everywhere. The same expression can be valid in a step if and invalid in a job if, which is why an expression that works in one workflow fails when moved.

Where you wrote itContexts available there
run-namegithub, inputs, vars
concurrencygithub, inputs, vars
Top-level envgithub, secrets, inputs, vars
jobs.<id>.ifgithub, needs, vars, inputs
jobs.<id>.steps.ifgithub, needs, strategy, matrix, job, runner, env, vars, steps, inputs
jobs.<id>.outputsFull access, including secrets
Reusable workflow outputsgithub, jobs, vars, inputs

Common causes

Secret not declared in workflow_call

A reusable workflow must list each secret it accepts under on.workflow_call.secrets. Passing an undeclared secret with secrets: { NAME: ... } is rejected.

Relying on inherit when explicit is needed

secrets: inherit forwards all caller secrets, but if you pass secrets explicitly the called workflow still must declare each one. Mixing the two incorrectly leaves a secret empty.

How to fix it

Declare secrets in the called workflow

deploy.yml (called)
# deploy.yml (called)
on:
  workflow_call:
    secrets:
      DEPLOY_TOKEN:
        required: true

Pass secrets explicitly or inherit

Either forward each declared secret by name, or use inherit to pass all caller secrets to a trusted called workflow.

.github/workflows/release.yml
# caller
jobs:
  call:
    uses: ./.github/workflows/deploy.yml
    secrets:
      DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
    # or, for a trusted callee: secrets: inherit

Catch it before it reaches CI

Every failure in this cluster is statically detectable. actionlint parses workflow expressions, checks context availability against the same rules above, and validates needs references, so these bugs never need to cost you a run.

Terminal
# one-off
docker run --rm -v "$(pwd):/repo" --workdir /repo rhysd/actionlint:latest -color

# as a job, before anything expensive runs
- uses: actions/checkout@v4
- run: |
    bash <(curl -s https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)
    ./actionlint -color

How to prevent it

  • Declare every secret a reusable workflow needs under workflow_call.secrets.
  • Pass secrets explicitly for least privilege; reserve inherit for trusted callees.
  • Document the secret contract alongside the inputs in the called workflow.

Frequently asked questions

What causes GitHub Actions reusable workflow "secret not defined"?
There are 2 common causes: secret not declared in workflow_call and relying on inherit when explicit is needed. A reusable workflow must list each secret it accepts under on.workflow_call.secrets.
How do I fix GitHub Actions reusable workflow "secret not defined"?
There are 2 fixes depending on which cause you have: declare secrets in the called workflow and pass secrets explicitly or inherit. Work through them in order, since the first is the most common.
What does GitHub Actions reusable workflow "secret not defined" actually mean?
The caller fails validation saying the secret is not defined in the called workflow, or the reusable workflow sees an empty secret because the caller did not pass it and did not use inherit.
How do I stop GitHub Actions reusable workflow "secret not defined" happening again?
Declare every secret a reusable workflow needs under workflow_call.secrets. The prevention section lists 3 changes that keep it from recurring.

Related guides

References

Not every red build is your code. Latchkey repairs the ones that are not, on the runner. Start free → 30-day trial · No credit card