secrets: inherit Not Working in Reusable Workflows - Fix
A called (reusable) workflow reads a secret as empty because the caller did not forward it. Reusable workflows do not see the caller's secrets automatically - each must be passed in the secrets: block or via secrets: inherit.
What this error means
A reusable workflow's step gets an empty secret value, so an API call or deploy fails, even though the secret exists in the calling repo. The caller never forwarded it to the called workflow.
# caller - secrets not forwarded
jobs:
build:
uses: ./.github/workflows/deploy.yml
# missing: secrets: inherit (or an explicit secrets: map)
# in deploy.yml, secrets.DEPLOY_TOKEN is emptyDiagnose it: what token do you actually have?
Permission failures in Actions are almost never about your repository settings alone. Three things combine: the default GITHUB_TOKEN permission set for the repo or organization, the permissions: block in the workflow, and whether the event is a fork pull request, which downgrades the token to read-only regardless of everything else.
- name: Show the token scopes actually granted
run: |
curl -sI -H "Authorization: Bearer $GITHUB_TOKEN" \
https://api.github.com/ | grep -i "^x-oauth-scopes\|^x-accepted"
echo "event: ${{ github.event_name }}"
echo "fork PR: ${{ github.event.pull_request.head.repo.fork }}"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}Common causes
Caller did not pass secrets
A reusable workflow only receives secrets the caller explicitly forwards. Omitting both the secrets: map and secrets: inherit leaves them undefined in the called workflow.
Called workflow did not declare the secret
When forwarding explicitly (not inherit), the called workflow must declare each secret under on.workflow_call.secrets, or it will not be available.
How to fix it
Forward secrets explicitly or inherit
Pass the needed secrets by name, or use secrets: inherit to forward all of the caller's secrets.
# caller
jobs:
deploy:
uses: ./.github/workflows/deploy.yml
secrets:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
# or, to pass everything:
# secrets: inheritDeclare secrets in the called workflow
- Add each forwarded secret under on.workflow_call.secrets in the called workflow.
- Use secrets: inherit only when the caller is trusted to expose all its secrets to the called workflow.
- For nested reusable workflows, forward at every level - inheritance is not transitive unless each level inherits.
Grant the narrowest permission that works
Declaring a permissions: block switches the job from the repository default to exactly what you list, so an incomplete block is a common cause of a new failure right after someone tightened security. List every scope the job needs, not just the one that failed.
permissions:
contents: read # checkout
packages: write # push to GHCR
id-token: write # OIDC to a cloud provider
pull-requests: write # comment on or label a PR
checks: write # publish check runsHow to prevent it
- Forward reusable-workflow secrets explicitly, or use secrets: inherit deliberately.
- Declare on.workflow_call.secrets in the called workflow.
- Forward at each nesting level; inheritance is not automatic deep down.