highIntermediateSymptom-first#secrets#env#masking#substitution

Secrets not substituted in workflow - empty or masked output

Step logs show empty string where a secret should be

Likely source:configurationsecurity

Focused incident mode — strips away everything but this runbook.

Symptoms

  • Step logs show empty string where a secret should be
  • Secret value appears as '***' in logs but the application receives an empty value
  • Environment variable from secrets is not accessible in a shell step

GitHub Actions secrets resolve to empty string (not an error) when undefined. Always validate secrets exist before relying on them in production workflows.

Evidence to grab

Collect these artifacts before you change anything — they prove (or rule out) each cause.

  • Secrets used in the workflow

    grep -n 'secrets\.' .github/workflows/<file>.yml
  • Environment variable mapping

    cat .github/workflows/<file>.yml | grep -B2 -A5 'env:'

Diagnostic flow

Run the steps in order, marking each claim confirmed or refuted. The evidence you gather feeds the ranked causes below.

Guided decision tree is ready.

Steps with commands, expected output, and pass/fail branches.

Ranked root causes

Evidence checklist

  • gh secret list does not show the secret name
  • Secret was added to a different repository or environment

Confirm before fixing

gh secret list --repo <OWNER/REPO> --env <ENVIRONMENT> | grep SECRET_NAME
READ

Check the environment-specific secret list

Fix

gh secret set SECRET_NAME -b '<VALUE>' --repo <OWNER/REPO>
CHANGE

Create the missing secret

Resolution runbook

Apply from top to bottom after the cause is confirmed. Review every command before running it — risk tags are shown on each.

1
grep -n 'secrets\.' .github/workflows/<file>.yml
READ

Find all secrets references

2
gh secret list --repo <OWNER/REPO>
READ

Check what secrets exist

3
gh secret set SECRET_NAME -b '<VALUE>' --repo <OWNER/REPO>
CHANGE

Create or update the secret

Prevention

  • Always define secrets in the repo settings before referencing them in workflows.
  • Use 'environment:' on production jobs to scope secrets to the right environment.
  • Add a validation step: if [ -z "$SECRET" ]; then echo "SECRET not set" && exit 1; fi.
  • Use GitHub Actions built-in masking to prevent accidental log exposure.

Commands marked READ CHANGE DESTRUCTIVE — never run DESTRUCTIVE commands against production data you cannot restore. Copy → verify → execute.