Environment Variables, Configuration Variables, Outputs, and Data Passing: Diagnostics, Failure Modes, and Production Practices
Dataflow failures are often misdiagnosed as “GitHub Actions lost my variable.” The real cause is usually a scope mismatch, a lifecycle misunderstanding, unsafe framing, or the wrong transport mechanism. This lesson preserves the original run evidence and diagnoses each layer separately before applying the smallest correction.
Learning objectives
- Diagnose missing values by reconstructing origin, scope, writer step, consumer job, and effective precedence.
- Explain why
GITHUB_ENVcannot cross jobs and why reserved/default environment variables should not be overwritten. - Recognize deprecated stdout output commands and use current environment-file output syntax.
- Diagnose multiline framing/quoting corruption without turning arbitrary input into runner control syntax.
- Replace oversized or file-shaped outputs with artifact/file transport while preserving first-failure evidence.
1. Evidence-first diagnostic sequence
- Preserve the run ID, attempt, exact workflow/source SHA, job/step names, and first failing/missing-value logs.
- Name the value and its intended producer and consumer.
- Locate its mechanism: YAML
env,vars, default environment,GITHUB_ENV, step output, job output, or file/artifact. - Check lifecycle/scope: same step, later step same job, downstream job, or later workflow.
- Check naming/precedence and exact output IDs.
- Check framing/encoding/size and whether the payload is sensitive or arbitrary.
- Apply one causal correction and rerun the smallest equivalent scope.
2. Failure: expecting GITHUB_ENV to cross jobs
jobs:
produce:
runs-on: ubuntu-24.04
steps:
- run: echo 'BUILD_LABEL=demo-17' >> "$GITHUB_ENV"
consume:
needs: produce
runs-on: ubuntu-24.04
steps:
- run: test "$BUILD_LABEL" = 'demo-17'
The consumer fails because the producer's environment-file update belongs only to later steps in produce. The needs edge orders jobs but does not share a process environment or filesystem.
Causal repair: publish BUILD_LABEL through GITHUB_OUTPUT, map it to a job output, and consume needs.produce.outputs.build_label. Do not add a self-hosted runner or shared disk to “fix” a missing interface.
3. Failure: trying to overwrite authoritative default variables
- name: Wrong mental model
run: |
echo 'GITHUB_SHA=fake-value' >> "$GITHUB_ENV"
echo 'RUNNER_OS=fake-os' >> "$GITHUB_ENV"
Current GitHub documentation says default GITHUB_* and RUNNER_* values cannot be overwritten this way. If you need a synthetic comparison value, give it an application-owned name such as EXPECTED_SHA. Never shadow evidence fields to make logs look convenient.
4. Failure: copying the deprecated stdout set-output pattern
# Historical pattern — do not use in new workflow code:
echo "::set-output name=version::1.2.3"
GitHub introduced environment files to reduce risks around workflow commands emitted through stdout. Current guidance is:
echo 'version=1.2.3' >> "$GITHUB_OUTPUT"
When reviewing old tutorials, distinguish “still observed in legacy workflows” from “recommended current design.” New course code uses GITHUB_OUTPUT.
5. Failure: delimiter collision corrupts multiline framing
This intentionally broken synthetic example chooses a delimiter that appears in the payload:
{
echo 'NOTE<> "$GITHUB_ENV"
The framing ends too early. If the payload is fully controlled, generate a collision-resistant delimiter. If the payload is arbitrary, current guidance is to write it to a file rather than betting on a delimiter that “probably” will not occur.
6. Failure: using outputs as a large/binary transport
Encoding a multi-megabyte report or binary into a step/job output creates unnecessary size, quoting, and integrity problems and may exceed output limits. Preserve the generated file, compute a digest if needed, transfer the file via artifact/file mechanism, and keep only the digest/name/version as a small output.
7. Failure: naming collision hides origin
env:
REGION: workflow-demo
jobs:
test:
env:
REGION: job-demo
steps:
- env:
REGION: step-demo
run: echo "$REGION"
The shell prints step-demo, but a reviewer may wrongly attribute it to repository configuration or workflow-level state. Fix the architecture by removing unnecessary collisions or naming layers explicitly. Do not diagnose precedence by printing entire contexts that may include sensitive values.
8. Failure: echoing sensitive data because “masking will handle it”
Configuration variables are unmasked, and secret masking is not a substitute for correct data handling. Never print a token, secret, private key, OIDC token, or derived credential merely to confirm transport. Prove the interface with synthetic stand-ins and use bounded metadata such as “credential present” only when safe and necessary.
9. Causal diagnosis matrix
| Symptom | Likely layer | Evidence | Smallest repair |
|---|---|---|---|
| Value missing in next step | Writer syntax/current-step visibility | GITHUB_ENV append + next-step log | Use correct env-file syntax; consume later step. |
| Value missing in next job | Scope/interface | Job graph + no mapped output | Step output → job output → needs. |
| Unexpected value wins | Precedence/naming | Bounded scope definitions | Remove collision or document authoritative owner. |
| Multiline truncates | Framing | Exact writer script and payload shape | Safe delimiter for controlled data or file for arbitrary data. |
| Output skipped/too large | Output contract | Runner warning, size, sensitivity classification | Reduce to small control value or use file/artifact. |
10. Intentionally broken mini-lab
In a disposable repository, run the cross-job GITHUB_ENV defect above and preserve the failed run. Then change only the producer/consumer interface to a job output. Keep trigger, runner, permissions, source data, and expected value identical. The repaired run proves causality because the transport mechanism is the only material change.
Record both run IDs/attempts, workflow SHAs, producer step conclusion, consumer failure/success, and the exact output name. Do not erase the original failed evidence.
11. Production practice: dataflow contracts are observable interfaces
Production workflow reviews should treat outputs and configuration like APIs: names are stable, producers and consumers are explicit, sensitivity is classified, sizes are bounded, and change history is reviewable. When a value disappears, debug the contract before changing runners, permissions, or external infrastructure.
Knowledge check
Why does adding needs: produce not make GITHUB_ENV values available to consume?
needs creates dependency ordering and output access; it does not share runner process environments or filesystems.
What is the current replacement for ::set-output in new workflow code?
Write name=value records to the per-step GITHUB_OUTPUT environment file.
Why is a fixed multiline delimiter unsafe for arbitrary payloads?
The payload can contain that delimiter on a line by itself, ending the value early; arbitrary content should be stored in a file.
A 20 MB JSON report must cross jobs. What should change?
Use file/artifact transport and expose only small metadata/digest as outputs if needed.
Why should you preserve the broken run before repair?
It is first-failure evidence proving the original scope/transport defect and lets the repaired run be compared causally.
Official references and version notes
- Variables — current distinction between workflow
env, configuration variables, default variables, and secrets. - Variables reference — current default variables, naming rules, precedence, configuration-variable limits, and context guidance.
- Store information in variables — current examples for workflow/job/step environment variables, configuration variables, contexts, and cross-step/job data flow.
- Workflow commands for GitHub Actions — current environment files including
GITHUB_ENV,GITHUB_OUTPUT, multiline syntax, and restrictions. - Passing information between jobs — current job-output mapping and
needs.<job>.outputsconsumption. - Workflow syntax for GitHub Actions — current
env, job outputs,needs, output size limits, and workflow syntax. - Store and share data with workflow artifacts — current artifact model for files that must outlive a step/job filesystem or are inappropriate for small outputs.
- GitHub Actions changelog: set-output update — why new workflow code should use environment files rather than the deprecated stdout
set-outputcommand.
Version-sensitive variable/output behavior was rechecked against current primary GitHub documentation on 2026-09-09. Executable labs use a disposable repository, ubuntu-24.04, built-in Bash steps only, and explicit permissions: {}; no external action, secret, package, deployment, self-hosted runner, or cloud account is required. At verification time, workflow/job/step env uses the most specific scope; default GITHUB_*/RUNNER_* environment variables cannot be overwritten; the step that writes GITHUB_ENV does not see the new value but later steps in the same job do; GITHUB_ENV cannot set NODE_OPTIONS; GITHUB_OUTPUT is the current step-output channel; multiline environment/output values use delimiter syntax whose delimiter must not occur on a line by itself; and arbitrary data is safer as a file rather than delimiter-encoded text. Configuration variables are non-secret, have organization/repository/environment scopes, and are subject to current limits including 48 KB per variable, 500 repository variables, 1,000 organization variables, 100 environment variables, and a 256 KB combined repository+organization payload per workflow run. Job outputs are limited to 1 MB per job and 50 MB total per workflow run (size approximated using UTF-16), so large/binary evidence belongs in files/artifacts rather than outputs. GitHub Actions is continuously delivered, so regenerate only after rechecking these limits and commands. The intentionally broken examples use only synthetic values. No example prints a real secret or recommends changing permissions/runners to hide a dataflow defect.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.