Skip to content
Latchkey

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

KeyContexts available
run-namegithub, inputs, vars
concurrencygithub, inputs, vars
env (top level)github, secrets, inputs, vars
jobs.<id>.ifgithub, needs, vars, inputs
jobs.<id>.runs-ongithub, needs, strategy, matrix, vars, inputs
jobs.<id>.steps.ifgithub, needs, strategy, matrix, job, runner, env, vars, steps, inputs
jobs.<id>.outputsFull access, including secrets
Reusable workflow outputsgithub, jobs, vars, inputs

The twelve contexts

ContextWhat it holds
githubEvent payload, ref, actor, repository, run metadata
envVariables set at workflow, job, or step level
varsConfiguration variables from repo, org, or environment
jobStatus and services of the current job
jobsReusable workflow job outputs only
stepsOutputs and status of completed steps that have an id
runnerOS, arch, temp paths, and tool cache of the runner
secretsSecret values available to the job
strategyMatrix strategy metadata such as job-index
matrixThe current matrix combination
needsOutputs and results of jobs this one depends on
inputsInputs of a workflow_call or workflow_dispatch workflow

The four mistakes this table explains

  • steps in a job-level if. Not available. The condition evaluates against an empty string and the job runs or skips unconditionally.
  • secrets in runs-on or a job if. Not available. Move the check into a step, or surface a non-secret flag through outputs.
  • inputs in a workflow with no workflow_call or workflow_dispatch block. The context does not exist, producing Unrecognized named-value: inputs.
  • env in a job-level if. Not available at that key. Use vars for configuration, or move the condition into a step.

Confirm what you actually have

.github/workflows/ci.yml
- 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

Terminal
docker run --rm -v "$(pwd):/repo" --workdir /repo rhysd/actionlint:latest -color

Diagnose 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.

.github/workflows/ci.yml
- 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 itContexts available there
run-namegithub, inputs, vars
concurrencygithub, inputs, vars
Top-level envgithub, secrets, inputs, vars
jobs.<id>.ifgithub, needs, vars, inputs
jobs.<id>.steps.ifgithub, needs, strategy, matrix, job, runner, env, vars, steps, inputs
jobs.<id>.outputsFull access, including secrets
Reusable workflow outputsgithub, 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.

Terminal
# 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 -color

Frequently asked questions

Why is my GitHub Actions context empty?
Because it is not available at that workflow key. GitHub resolves an unavailable context to an empty string rather than failing, so the expression evaluates against nothing and the job silently behaves as if the condition were false.
Why can I use steps.x in one if and not another?
The 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"?
Referencing the 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?
No. 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.

Related guides

References

Run this faster and cheaper on Latchkey managed runners - self-healing included. Start free → 30-day trial · No credit card