Anything inside ${{ }} is an expression, evaluated by GitHub 29 before the step reaches the runner. It reads contexts: github (event, ref, SHA, actor, and the webhook payload as github.event), inputs, env, vars, secrets, steps, needs, runner, matrix and a few more; runs-on and a job's if cannot use env.
context:
runs-on: ${{ inputs.runner || 'ubuntu-24.04' }}
steps:
- name: Show some context values
env:
EVENT: ${{ github.event_name }}
REF: ${{ github.ref }}
PR: ${{ github.event.pull_request.number || 'none' }}
RUNNER: ${{ format('{0}/{1}', runner.os, runner.arch) }}
ON_MAIN: ${{ github.ref == 'refs/heads/main' }}
run: |
echo "event=$EVENT ref=$REF pr=$PR on_main=$ON_MAIN"
echo "runner=$RUNNER image=$ImageOS/$ImageVersion node=$(node --version)"It printed these lines in the runs for pull request #16, its merge, and the manual run on ubuntu-26.04:
event=pull_request ref=refs/pull/16/merge pr=16 on_main=false runner=Linux/X64 image=ubuntu24/20260920.314.1 node=v22.23.2 event=push ref=refs/heads/main pr=none on_main=true runner=Linux/X64 image=ubuntu24/20260920.314.1 node=v22.23.2 event=workflow_dispatch ref=refs/heads/main pr=none on_main=true runner=Linux/X64 image=ubuntu26/20260920.143.1 node=v24.21.0
On a pull request github.ref is the test-merge ref of Actions and Git Storage. github.event.pull_request is null for other events, and || returns its first truthy operand, hence none. Functions include contains, startsWith, fromJSON, hashFiles and toJSON (toJSON(github) dumps the whole object while you debug).