Skip to content
Latchkey

GitHub Actions Environment Secrets Empty Without environment: on the Job

A secret defined at the environment level is empty in your job because the job did not declare environment:, so only repository and org secrets are available.

What this error means

secrets.MY_SECRET resolves to an empty string even though it is set under an environment. Moving the secret to repository scope, or adding environment: to the job, makes it appear.

.github/workflows/deploy.yml
jobs:
  deploy:
    runs-on: ubuntu-latest
    # no environment: declared, so the "production" environment secret is empty
    steps:
      - run: echo "len=${#TOKEN}"
        env:
          TOKEN: ${{ secrets.PROD_TOKEN }}

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

.github/workflows/ci.yml
- 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

Job did not declare the environment

Environment-scoped secrets only load for jobs that reference that environment. Without environment: production, the production secrets are not in scope.

Secret stored at the wrong scope

A secret meant to be broadly available was stored only on one environment, so jobs without that environment cannot read it.

How to fix it

Declare the environment on the job

.github/workflows/deploy.yml
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - run: ./deploy.sh
        env:
          TOKEN: ${{ secrets.PROD_TOKEN }}

Choose the right secret scope

  1. Use environment secrets for values gated by environment protection.
  2. Use repository or org secrets for values every job needs.
  3. Avoid duplicating the same secret across scopes unless intentional.

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.

.github/workflows/ci.yml
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 runs

How to prevent it

  • Declare environment: on any job that reads environment-scoped secrets.
  • Pick secret scope deliberately: environment vs repository vs org.
  • Confirm a secret length in a debug step when wiring up a new deploy.

Frequently asked questions

What causes GitHub Actions environment secrets empty without environment: on the job?
There are 2 common causes: job did not declare the environment and secret stored at the wrong scope. Environment-scoped secrets only load for jobs that reference that environment.
How do I fix GitHub Actions environment secrets empty without environment: on the job?
There are 2 fixes depending on which cause you have: declare the environment on the job and choose the right secret scope. Work through them in order, since the first is the most common.
What does GitHub Actions environment secrets empty without environment: on the job actually mean?
secrets.MY_SECRET resolves to an empty string even though it is set under an environment.
How do I stop GitHub Actions environment secrets empty without environment: on the job happening again?
Declare environment: on any job that reads environment-scoped 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