Study guide

3.3 Environment Variables & Workflow Commands

GitHub Actions Certification GH-200

Track your progress as you read

Finished sections get a check mark.

1 Chapter 1: Introduction & Exam Overview

1.1 Exam Structure, Format & Scoring 1.2 GitHub Actions Architecture & Core Mental Model 1.3 Hands-On Preparation & Exam Day Strategy

2 Chapter 2: Workflow Triggers & Event Architecture

2.1 Webhook & Code Events: push, pull_request, and pull_request_target 2.2 Scheduled & Manual Triggers: schedule, workflow_dispatch, and repository_dispatch 2.3 Release, Issue, and Dynamic Lifecycle Events 2.4 Branch, Tag, and Path Filtering Patterns

3 Chapter 3: Jobs, Matrix Strategies & Execution Control

3.1 Job Anatomy, Step Execution & Job Dependencies (needs) 3.2 Contexts, Expressions & Functions 3.3 Environment Variables & Workflow Commands 3.4 Matrix Strategies & Multi-Platform Builds 3.5 YAML Anchors, Aliases & Merge Keys

4 Chapter 4: Reusable Workflows & Troubleshooting

4.1 Authoring & Calling Reusable Workflows (workflow_call) 4.2 Comparing Reusable Workflows, Composite Actions & Starter Workflows 4.3 Troubleshooting Workflow Runs & Debug Logging 4.4 Concurrency Controls & Status Check Functions

5 Chapter 5: Authoring Custom Actions

5.1 Action Architecture & action.yml Specification 5.2 Authoring Composite Actions 5.3 Building JavaScript Actions 5.4 Building Docker Container Actions

6 Chapter 6: Action Distribution, Marketplace & Advanced Action Types

6.1 Action Versioning, Semantic Releases & Tagging 6.2 Publishing Actions to GitHub Marketplace 6.3 Service Containers & Network Topology 6.4 actions/github-script & Workflow Tooling

7 Chapter 7: Runner Infrastructure & Self-Hosted Runners

7.1 GitHub-Hosted Runners & Larger Runners 7.2 Self-Hosted Runner Setup, Architecture & Communication 7.3 Self-Hosted Runner Security & Ephemeral Runners 7.4 Runner Groups, Labels & Organization Routing

8 Chapter 8: Enterprise Governance, Policies & Environments

8.1 Enterprise & Organization Action Policies 8.2 Environments, Protection Rules & Deployment Approvals 8.3 Repository Rulesets, Required Status Checks & Governance 8.4 Usage Limits, Billing Multipliers & Usage Metrics

9 Chapter 9: Secrets, Permissions & OIDC Cloud Authentication

9.1 Encrypted Secrets Architecture & Scopes 9.2 GITHUB_TOKEN & Least Privilege Configuration 9.3 OpenID Connect (OIDC) & Workload Identity Federation 9.4 Script Injection Prevention & Secure Workflow Patterns

10 Chapter 10: Supply Chain Hardening & Performance Optimization

10.1 Supply Chain Hardening: SHA Pinning, Dependabot & Artifact Attestations 10.2 Caching Dependencies with `actions/cache` 10.3 Artifacts Management & Job Handoff 10.4 Workflow Performance, Cost Optimization & Architecture Review

3.3 Environment Variables & Workflow Commands

Key Takeaways Explain This Topic AI

Environment variables follow a strict hierarchical precedence: step env overrides job env, which overrides workflow env, which overrides default runner environment variables.

Environment variables are made persistent across subsequent steps by appending key-value strings to the runner environment file via echo 'KEY=val' >> '$GITHUB_ENV'.

Multi-line environment variables must be written to $GITHUB_ENV using a unique delimiter string (EOF heredoc syntax) to prevent corruption or truncation.

Step output parameters are exported using echo 'name=val' >> '$GITHUB_OUTPUT' and referenced in later steps via ${{ steps.<step_id>.outputs.name }}.

Special workflow commands enable dynamic secret masking (::add-mask::), path modification ( $GITHUB_PATH ), and rich Markdown job summaries ( $GITHUB_STEP_SUMMARY ).

Last updated: August 2026

Environment Variables & Workflow Commands

Managing data flow and runner state during a workflow run requires a solid grasp of environment variable scoping , precedence hierarchies , and workflow commands . In GitHub Actions, runners communicate with the execution engine through special file-based interfaces ( $GITHUB_ENV , $GITHUB_OUTPUT , $GITHUB_PATH , $GITHUB_STEP_SUMMARY ) and stdout logging commands.

1. Environment Variable Precedence Hierarchy

Environment variables can be declared at multiple levels in a workflow, as well as provided natively by GitHub and the runner environment. When variables share the same name, GitHub Actions resolves them using a strict precedence hierarchy. +-----------------------------------------------------------------------------+

  • | ENVIRONMENT VARIABLE PRECEDENCE HIERARCHY |
  • | |
  • | [HIGHEST PRECEDENCE] |
  • | |
  • | 1. Step Level `steps[*].env` |
  • | | |
  • | v |
  • | 2. Job Level `jobs.<job_id>.env` |
  • | | |
  • | v |
  • | 3. Workflow Level `env` (top-level) |
  • | | |
  • | v |
  • | 4. Default Variables `GITHUB_*`, `RUNNER_*` (Set by execution engine) |
  • | | |
  • | v |
  • | 5. Configuration `vars.*` (Repository / Org Settings) |
  • | |
  • | [LOWEST PRECEDENCE] |
  • +-----------------------------------------------------------------------------+
  • Precedence Comparison Table

Level / Source YAML / Engine Scope Example Declaration Scope of Visibility

Step Level Highest priority. Overrides all higher scopes for that specific step. `steps:

env:

APP_ENV: staging | Only the specific step where declared. | | **Job Level** | Overrides workflow-level variables for all steps in the job. | jobs:

build:

env:

APP_ENV: test | All steps inside that specific job. | | **Workflow Level** | Global across all jobs in the workflow file. | env:

APP_ENV: production | All jobs and all steps in the workflow. | | **Default Runner Vars** | Injected automatically by the runner environment. | GITHUB_SHA , GITHUB_REF , RUNNER_OS | Global across all steps in all jobs. | | **Configuration Vars** | Stored at repo/org level; referenced via vars.* . | ${{ vars.DEFAULT_REGION }}` | Explicitly injected via YAML. | env:

NODE_ENV: production # Workflow level

jobs:

build:

runs-on: ubuntu-latest

env:

NODE_ENV: staging # Job level (overrides workflow level)

steps:

  • name: Step 1 (Inherits Job Level)
  • run: echo "NODE_ENV is $NODE_ENV" # Outputs: staging

- name: Step 2 (Step Override)

env:

  • NODE_ENV: development # Step level (overrides job level)
  • run: echo "NODE_ENV is $NODE_ENV" # Outputs: development
  • name: Step 3 (Returns to Job Level)
  • run: echo "NODE_ENV is $NODE_ENV" # Outputs: staging
  • 2. Modern File-Based Workflow Commands

In modern GitHub Actions runner environments, state changes intended to persist beyond the current step are communicated by appending data to temporary files whose paths are exposed as environment variables. +-----------------------------------------------------------------------------+

  • | RUNNER ENVIRONMENT FILES OVERVIEW |
  • | |
  • | $GITHUB_ENV ---> Sets environment variables for SUBSEQUENT steps |
  • | $GITHUB_OUTPUT ---> Sets step output parameters for steps & jobs |
  • | $GITHUB_PATH ---> Prepends directories to the system PATH |
  • | $GITHUB_STEP_SUMMARY ---> Appends Markdown to the Workflow Summary UI |
  • +-----------------------------------------------------------------------------+
  • 1. Setting Environment Variables: $GITHUB_ENV

To make an environment variable available to subsequent steps in the same job, append the KEY=value pair to the file located at $GITHUB_ENV . # Single-line assignment

echo "DEPLOY_TAG=v1.2.3" >> "$GITHUB_ENV"

Note

Variables written to $GITHUB_ENV are not visible within the currently executing step's shell environment; they become active starting with the next step in the job.

Multi-Line Environment Variables (Heredoc Syntax):

When setting multi-line strings (e.g., certificates, multiline release notes, JSON strings), you must use a delimiter string: echo 'RELEASE_NOTES<<EOF' >> "$GITHUB_ENV"

  • echo "## Changelog" >> "$GITHUB_ENV"
  • echo "- Fixed authentication bug" >> "$GITHUB_ENV"
  • echo "- Added ARM64 support" >> "$GITHUB_ENV"
  • echo 'EOF' >> "$GITHUB_ENV"
  • 2. Setting Step Outputs: $GITHUB_OUTPUT

Step outputs provide structured parameters accessible by later steps via ${{ steps.<step_id>.outputs.<key> }} or exported to downstream jobs: # Single-line output

echo "image_digest=sha256:7f83b1657" >> "$GITHUB_OUTPUT"

  • # Multi-line output
  • echo 'config_json<<EOF' >> "$GITHUB_OUTPUT"
  • echo '{"env": "prod", "replicas": 5}' >> "$GITHUB_OUTPUT"
  • echo 'EOF' >> "$GITHUB_OUTPUT"
  • 3. Adding to System PATH: $GITHUB_PATH

To prepend a directory to the system PATH for all subsequent steps in the current job: echo "/opt/custom-toolchain/bin" >> "$GITHUB_PATH"

  • echo "$HOME/.local/bin" >> "$GITHUB_PATH"
  • 4. Custom Markdown Job Summaries: $GITHUB_STEP_SUMMARY

Workflows can render rich Markdown reports on the GitHub Actions run summary page by appending content to $GITHUB_STEP_SUMMARY (up to 1,024 KB per step): echo "### 🧪 Test Execution Report" >> "$GITHUB_STEP_SUMMARY"

  • echo "| Suite | Passed | Failed |" >> "$GITHUB_STEP_SUMMARY"
  • echo "| :--- | :--- | :--- |" >> "$GITHUB_STEP_SUMMARY"
  • echo "| Unit | 142 | 0 |" >> "$GITHUB_STEP_SUMMARY"
  • echo "| Integration | 38 | 0 |" >> "$GITHUB_STEP_SUMMARY"
  • 3. Workflow Log Commands & Secret Masking

Workflow commands can also be printed directly to stdout using specific formatted strings that the runner interceptor parses.

Dynamic Secret Masking ( ::add-mask:: )

When a script generates or retrieves a sensitive token dynamically at runtime, printing ::add-mask:: instructs the runner to redact all future instances of that string from the logs, replacing them with *** . DYNAMIC_TOKEN=$(curl -s https://auth.internal.corp/generate-token)

  • echo "::add-mask::$DYNAMIC_TOKEN"
  • echo "TOKEN=$DYNAMIC_TOKEN" >> "$GITHUB_ENV"
  • Log Annotations: Notices, Warnings, and Errors

Commands can create inline annotations on GitHub Pull Request diffs and workflow logs: # Format: ::warning file={name},line={line},col={col}::{message}

  • echo "::warning file=src/app.js,line=42,col=5::Deprecated API usage detected"
  • echo "::error file=src/db.js,line=100::Database connection timeout"
  • echo "::notice::Build completed in 45s"
  • Log Grouping ( ::group:: and ::endgroup:: )

Collapsible sections in the runner web console keep log output clean and organized: echo "::group::Initializing Dependencies"

  • npm ci
  • echo "::endgroup::"
  • 4. Deprecated Workflow Commands & Security Remediation

In older versions of GitHub Actions, commands were executed via stdout syntax such as ::set-output and ::set-env . These were deprecated and disabled by default due to critical security injection vulnerabilities. +-----------------------------------------------------------------------------+

  • | DEPRECATED VS MODERN COMMAND MIGRATION |
  • | |
  • | [DEPRECATED / INSECURE] [MODERN / SECURE] |
  • | |
  • | echo "::set-output name=x::val" ---> echo "x=val" >> "$GITHUB_OUTPUT" |
  • | echo "::set-env name=x::val" ---> echo "x=val" >> "$GITHUB_ENV" |
  • | echo "::add-path::/custom/bin" ---> echo "/bin" >> "$GITHUB_PATH" |
  • +-----------------------------------------------------------------------------+

Warning

Security Vulnerability of ::set-env and ::set-output : If an untrusted input (such as an issue title, commit message, or PR comment) contained ::set-env name=NODE_OPTIONS::--inspect , the stdout parser would blindly execute the command, allowing arbitrary environment variable injection and remote code execution (RCE). The file-based system isolates commands to dedicated file handles that untrusted stdout output cannot trigger.

5. Comprehensive YAML Workflow Example name: Enterprise Workflow Commands Demo

on:

push:

branches: [main]

jobs:

process-artifacts:

runs-on: ubuntu-latest

steps:

  • name: Checkout Code
  • uses: actions/checkout@v4
  • name: Configure Environment and Paths
  • id: setup
  • run: |
  • echo "::group::Setup Toolchain and Masks"
  • # 1. Mask sensitive token
  • API_SECRET="super-secret-token-xyz-99"
  • echo "::add-mask::$API_SECRET"
  • # 2. Add local bin to PATH
  • mkdir -p $HOME/.custom_bin
  • echo "$HOME/.custom_bin" >> "$GITHUB_PATH"
  • # 3. Set persistent environment variable
  • echo "APP_STAGE=canary" >> "$GITHUB_ENV"
  • # 4. Set step output
  • echo "calculated_id=CANARY-9842" >> "$GITHUB_OUTPUT"
  • echo "::endgroup::"
  • name: Validate Persistence & Generate Summary
  • run: |
  • # Verify APP_STAGE is available from $GITHUB_ENV
  • echo "Current Stage is: $APP_STAGE"
  • # Verify step output from setup step
  • echo "Calculated ID: ${{ steps.setup.outputs.calculated_id }}"
  • # Write markdown summary to GitHub UI
  • echo "## 🚀 Deployment Status" >> "$GITHUB_STEP_SUMMARY"
  • echo "- Stage: **$APP_STAGE**" >> "$GITHUB_STEP_SUMMARY"
  • echo "- Deployment ID: `${{ steps.setup.outputs.calculated_id }}`" >> "$GITHUB_STEP_SUMMARY"