What this usually means
GitHub Actions secrets are scoped. They can be at the repository level, the organisation level, or the environment level. A workflow can only access secrets that are in scope for the event that triggered it. Secrets are not passed to workflows triggered by pull_request from forks for security reasons. Environment-scoped secrets require the job to reference the environment explicitly. Secrets cannot be passed directly between jobs.
The first ten minutes \u2014 establish facts before touching code.
- 1Check the secret scope. Is it a repository secret, organisation secret, or environment secret?
- 2Check if the workflow is triggered by pull_request from a fork. Secrets are not available to fork PRs.
- 3Check if the secret name matches exactly. Secrets are case-sensitive. DATABASE_URL is not database_url.
- 4Check if the workflow job references an environment block. If so, only that environment's secrets are available.
- 5Debug by testing if the secret is empty: add a step that checks whether the secret value length is zero.
The specific files, logs, configs, and dashboards that usually own this bug.
- searchGitHub repository Settings → Secrets and variables → Actions
- searchWorkflow file in .github/workflows/ — secret references and environment configuration
- searchWorkflow run logs — the exact error or empty value behaviour
- searchEnvironment configuration — Settings → Environments → environment name → Secrets
- searchOrganisation secrets — if using org-level secrets, check they are available to this repo
Practical causes, not theory. These are the things you will actually find.
- warningSecret is defined at the environment level but the job does not specify environment:
- warningSecret is defined at the organisation level but not made available to this repository
- warningWorkflow is triggered by pull_request from a fork — secrets are not available
- warningSecret name has a typo, wrong case, or extra whitespace
- warningSecret is referenced in a job but that job does not have access to the secret scope
- warningSecret value was not saved after being added to the repository settings
Concrete fix directions. Pick the one that matches your root cause.
- buildVerify the secret exists in the correct scope: repo Settings → Secrets → Actions
- buildAdd environment: <name> to the job if the secret is environment-scoped
- buildFor PR workflows that need secrets, use pull_request_target instead of pull_request (with caution)
- buildUse organisation secrets for values shared across multiple repositories
- buildTest secret availability with a harmless debug step before using it in production steps
A fix you cannot prove is a guess. Close the loop.
- verifiedTrigger the workflow. The step that uses the secret should succeed without errors.
- verifiedTest with a non-sensitive test secret first to verify the mechanism works.
- verifiedAfter adding or updating a secret, re-run the workflow. Secrets are injected at workflow start.
- verifiedCheck the workflow run logs. GitHub masks secret values in logs automatically.
Things that make this bug worse or harder to find.
- warningAccidentally printing the secret value in workflow logs
- warningNot scoping secrets to environments when they are environment-specific
- warningUsing the same secret name for different values across environments
- warningAssuming secrets are available in PR workflows from forks
- warningNot testing the workflow after adding a new secret