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

> Fix GitHub Actions reusable workflow secret errors - passing a secret the called workflow never declared under workflow_call.secrets, or relying on inherit incorrectly.

Source: https://latchkey.dev/learn/github-actions/gha-reusable-secret-not-declared  
Updated: 2026-06-25

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.

## 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) }}'
```

> An empty `{}` or a blank line is the finding. It means the context is not populated at that point, which is a different problem from the value being wrong, and it needs a different fix.

## 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 it | Contexts available there |
| --- | --- |
| `run-name` | `github`, `inputs`, `vars` |
| `concurrency` | `github`, `inputs`, `vars` |
| Top-level `env` | `github`, `secrets`, `inputs`, `vars` |
| `jobs.<id>.if` | `github`, `needs`, `vars`, `inputs` |
| `jobs.<id>.steps.if` | `github`, `needs`, `strategy`, `matrix`, `job`, `runner`, `env`, `vars`, `steps`, `inputs` |
| `jobs.<id>.outputs` | Full access, including `secrets` |
| Reusable workflow `outputs` | `github`, `jobs`, `vars`, `inputs` |

> The most common trap in this table: `steps` and `matrix` are available in a **step** `if` but not in a **job** `if`. Moving a condition up a level silently breaks it.

## 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
```

## FAQ

### 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.

---

Latchkey runs CI/CD that repairs its own failures. Agent entry points: https://latchkey.dev/agent.txt, https://latchkey.dev/openapi.json, https://latchkey.dev/llms.txt
