Copyright (c) 2026 MindMesh Academy. All rights reserved. This content is proprietary and may not be reproduced or distributed without permission.

2.2.2. Environment Variables and Workflow Commands

💡 First Principle: A step can talk back to the runner — setting variables, masking values, grouping logs, creating annotations — through two channels: environment files (append a line to a special file) and workflow commands (echo a specially formatted string). Knowing which channel does what is the difference between data that persists and data that vanishes.

Environment variables come from a three-level hierarchy, with the innermost winning: workflow-level env:, job-level env:, then step-level env:. Layered on top are default variables GitHub always provides — GITHUB_REPOSITORY, GITHUB_REF, GITHUB_SHA, GITHUB_WORKSPACE, GITHUB_RUN_ID, RUNNER_OS and friends — which you may read but must not overwrite.

To create a variable that outlives the current step, append to the GITHUB_ENV file:

- name: Compute version
  run: echo "APP_VERSION=1.4.$GITHUB_RUN_NUMBER" >> "$GITHUB_ENV"
- name: Use it
  run: echo "Building $APP_VERSION"        # available here, not in the step above

The environment-file family is the modern, supported mechanism, and each file has a distinct job:

File / commandPurpose
$GITHUB_ENVSet an env var for subsequent steps in this job
$GITHUB_OUTPUTSet a step output consumed via steps.<id>.outputs.<name>
$GITHUB_PATHPrepend a directory to PATH for subsequent steps
$GITHUB_STEP_SUMMARYAppend Markdown to the run's summary page
::error file=…,line=…::msgCreate an error annotation on the run and in the diff
::warning:: / ::notice::Lower-severity annotations
::group:: / ::endgroup::Collapsible log sections
::add-mask::valueRegister a value so it is ***-redacted in all logs
::debug::Message shown only when ACTIONS_STEP_DEBUG is enabled

For multiline values, use the delimiter form, because a bare KEY=line1\nline2 corrupts the file:

- run: |
    {
      echo 'NOTES<<EOF'
      cat release-notes.md
      echo 'EOF'
    } >> "$GITHUB_ENV"

⚠️ Exam Trap: A variable written to $GITHUB_ENV is not visible in the step that wrote it — environment files are processed between steps. Within the same step you must use an ordinary shell variable. Equally: ::set-output and ::set-env were deprecated in 2022 and disabled in 2023; any answer using them is wrong on a current exam.

Reflection Question: ::add-mask:: redacts a value from logs, but GitHub warns it isn't a substitute for treating the value as secret. What can still leak a masked value out of a run?

See how it connects
Alvin Varughese
Written byAlvin Varughese
Founder18 professional certifications