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

2.2.6. Contexts and Expressions

💡 First Principle: Contexts are the run's data model — structured objects describing the event, the repository, the runner, and the state of prior steps — and ${{ }} expressions are the only way to read them, evaluated by GitHub before the shell ever runs.

That last clause is the crux of both correct behavior and the security lesson in 6.1.2. ${{ }} is textual substitution performed by the workflow engine at evaluation time; by the time your run: script executes, the expression is gone and its value is sitting in the script as literal text.

The contexts you must recognize on sight:

ContextHoldsAvailability note
githubEvent payload, repo, ref, sha, actor, run_idEverywhere
envVariables from env: blocksNot available in env: itself at workflow level
varsConfiguration variables (org/repo/env)Everywhere
secretsEncrypted secretsNot in runs-on, not in composite action steps directly
inputsworkflow_dispatch / workflow_call / action inputsOnly when inputs are defined
matrixCurrent variant's valuesInside matrix jobs only
needsUpstream jobs' outputs and resultJobs with needs:
stepsPrior steps' outputs, outcome, conclusionAfter the step has an id:
job / runnerJob status, services; runner OS, arch, temp dirsJob scope / step scope
strategyjob-index, fail-fast, job-totalMatrix jobs

Expression syntax supports comparison (==, !=, <), logic (&&, ||, !), and a small function library that shows up constantly: contains(), startsWith(), endsWith(), format(), join(), toJSON(), fromJSON(), hashFiles(), plus the four status functions from 2.2.1.

if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
- run: echo "Key ${{ hashFiles('**/package-lock.json') }}"

Static vs. runtime evaluation is the concept the objectives name directly. Some keys are resolved when the run is created — runs-on, services, job-level if — so they cannot see values produced later in the run (this is why secrets isn't allowed in runs-on). Step-level expressions resolve as the job progresses, so they can read steps.*.outputs from earlier steps. When a scenario shows an expression that "always evaluates to empty," the answer is usually that it was evaluated before the value existed.

⚠️ Exam Trap: Secrets are masked in logs, but masking only catches exact matches of the registered value. Transforming a secret — base64-encoding it, embedding it in a URL, printing it character-spaced — defeats masking. Never interpolate a secret into a place it can be reshaped, and never pass one through an expression into a shell command line where it lands in process listings.

Reflection Question: Why can runs-on: not reference secrets or a step output, while if: on a step can reference both? Tie your answer to when each key must be resolved.

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