GitHub Actions Contexts: Where Each One Is Actually Available
Contexts are not available everywhere, and referencing one where it is not available does not fail the run. It resolves to an empty string, so the workflow continues with a silently wrong value.
Almost every confusing GitHub Actions expression bug comes down to one rule: a context that is not available at that key resolves to an empty string rather than raising an error. Your if: condition does not fail, it just evaluates against nothing, and the job silently runs or silently skips.
That is why the same expression can work in a step and break when moved to the job level. This page is the availability table plus the specific mistakes it explains.
Availability by workflow key
| Key | Contexts available |
|---|---|
run-name | github, inputs, vars |
concurrency | github, inputs, vars |
env (top level) | github, secrets, inputs, vars |
jobs.<id>.if | github, needs, vars, inputs |
jobs.<id>.runs-on | github, needs, strategy, matrix, vars, inputs |
jobs.<id>.steps.if | github, needs, strategy, matrix, job, runner, env, vars, steps, inputs |
jobs.<id>.outputs | Full access, including secrets |
Reusable workflow outputs | github, jobs, vars, inputs |
The twelve contexts
| Context | What it holds |
|---|---|
github | Event payload, ref, actor, repository, run metadata |
env | Variables set at workflow, job, or step level |
vars | Configuration variables from repo, org, or environment |
job | Status and services of the current job |
jobs | Reusable workflow job outputs only |
steps | Outputs and status of completed steps that have an id |
runner | OS, arch, temp paths, and tool cache of the runner |
secrets | Secret values available to the job |
strategy | Matrix strategy metadata such as job-index |
matrix | The current matrix combination |
needs | Outputs and results of jobs this one depends on |
inputs | Inputs of a workflow_call or workflow_dispatch workflow |
The four mistakes this table explains
stepsin a job-levelif. Not available. The condition evaluates against an empty string and the job runs or skips unconditionally.secretsinruns-onor a jobif. Not available. Move the check into a step, or surface a non-secret flag throughoutputs.inputsin a workflow with noworkflow_callorworkflow_dispatchblock. The context does not exist, producingUnrecognized named-value: inputs.envin a job-levelif. Not available at that key. Usevarsfor configuration, or move the condition into a step.
Confirm what you actually have
- name: Dump contexts
run: |
echo 'github:'; echo '${{ toJSON(github) }}'
echo 'needs:'; echo '${{ toJSON(needs) }}'
echo 'steps:'; echo '${{ toJSON(steps) }}'
echo 'matrix:'; echo '${{ toJSON(matrix) }}'
echo 'inputs:'; echo '${{ toJSON(inputs) }}'Catch it before it costs a run
docker run --rm -v "$(pwd):/repo" --workdir /repo rhysd/actionlint:latest -colorDiagnose it: print the context before you change anything
Most workflow-expression bugs are not syntax errors, they are an expression reading something that is empty. GitHub resolves a missing property to an empty string instead of failing the run, so a wrong reference looks like a logic bug rather than a mistake. Dump the contexts first and you will usually see the answer immediately.
- name: Dump contexts
run: |
echo '--- github ---' ; echo '${{ toJSON(github) }}'
echo '--- needs ---' ; echo '${{ toJSON(needs) }}'
echo '--- steps ---' ; echo '${{ toJSON(steps) }}'
echo '--- matrix ---' ; echo '${{ toJSON(matrix) }}'
echo '--- inputs ---' ; echo '${{ toJSON(inputs) }}'Check the context is allowed where you used it
Contexts are not available everywhere. The same expression can be valid in a step if and invalid in a job if, which is why an expression that works in one workflow fails when moved.
| Where you wrote it | Contexts available there |
|---|---|
run-name | github, inputs, vars |
concurrency | github, inputs, vars |
Top-level env | github, secrets, inputs, vars |
jobs.<id>.if | github, needs, vars, inputs |
jobs.<id>.steps.if | github, needs, strategy, matrix, job, runner, env, vars, steps, inputs |
jobs.<id>.outputs | Full access, including secrets |
Reusable workflow outputs | github, jobs, vars, inputs |
Catch it before it reaches CI
Every failure in this cluster is statically detectable. actionlint parses workflow expressions, checks context availability against the same rules above, and validates needs references, so these bugs never need to cost you a run.
# one-off
docker run --rm -v "$(pwd):/repo" --workdir /repo rhysd/actionlint:latest -color
# as a job, before anything expensive runs
- uses: actions/checkout@v4
- run: |
bash <(curl -s https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)
./actionlint -colorFrequently asked questions
Why is my GitHub Actions context empty?
Why can I use steps.x in one if and not another?
steps context is available in jobs.<id>.steps.if but not in jobs.<id>.if. A condition that works inside a step breaks silently when moved to the job level.What causes "Unrecognized named-value: inputs"?
inputs context in a workflow that declares no workflow_call or workflow_dispatch inputs. The context does not exist there at all, which is one of the few cases GitHub reports as an error rather than an empty string.Can I use secrets in runs-on or a job-level if?
secrets is not available at either key. Move the check into a step, or expose a non-secret boolean through a job output and branch on that instead.