Actions Foundations

The vocabulary and the run model every other page builds on: what starts a run, what a job and a step are, where each file lives, what the Actions tab shows you, when each piece is evaluated — and which names and commands older material still uses.

Page
Prerequisite
Exam
GH-200
Skills measured
January 2026
Sections
8

What GitHub Actions is

GitHub Actions is GitHub's built-in automation platform. You describe automation as YAML workflow files committed to the repository, and GitHub runs them on runners in response to events — a push, a pull request, a schedule, a button press, an API call or almost any activity on GitHub. CI/CD is the obvious use, but the same machinery labels issues, publishes packages, rotates dependencies, deploys Pages sites and answers webhooks from other systems.

Because workflows live in the repository, they are versioned, reviewed and branched like code. That is the root of many exam answers: a workflow change goes through a pull request, a workflow on a branch runs that branch's version of the file, and protecting .github/workflows/ with CODEOWNERS is how you control who may change automation.

The building blocks

flowchart LR
  EV["Event"] --> WF["Workflow run"]
  WF --> J1["Job A"]
  WF --> J2["Job B"]
  J1 --> R1["Runner<br/>fresh VM or container"]
  J2 --> R2["Runner<br/>fresh VM or container"]
  R1 --> S1["Steps<br/>run: scripts · uses: actions"]
  R2 --> S2["Steps<br/>run: scripts · uses: actions"]
An event starts a workflow run. Each job gets its own fresh runner, so jobs share nothing unless you pass it explicitly — artifacts, outputs or a cache. Steps within a job share the runner's filesystem and environment.
TermWhat it isRemember
EventActivity that triggers a run: push, pull_request, schedule, workflow_dispatch, repository_dispatch, workflow_call, issues…Declared under on:; many events have types and filters
WorkflowA YAML file in .github/workflows/ with one or more jobsOnly .yml or .yaml files in that exact directory run
Workflow runOne execution of a workflow, with a run ID, run number and attempt numberRe-running increments github.run_attempt, not run_number
JobA set of steps that runs on one runnerJobs run in parallel by default; needs: makes them sequential
StepOne run: script or one uses: actionSteps run in order; a failing step fails the job unless continue-on-error
ActionA reusable unit of steps: JavaScript, Docker container or compositeReferenced as owner/repo@ref, ./path or docker://image
RunnerThe machine that executes a job — GitHub-hosted, larger, or self-hostedSelected by runs-on labels, groups or both
Reusable workflowA workflow with on: workflow_call, called as a job with jobs.<id>.usesCalled at job level; a composite action is used at step level
EnvironmentA named deployment target with protection rules, secrets and variablesA job opts in with environment:

Anatomy of a workflow

Every workflow you meet on the exam is some arrangement of the same few top-level keys. Learn to read them in this order: when does it run (on), with what power (permissions), how many at once (concurrency), and then the jobs and their dependencies.

.github/workflows/ci.yml
name: CI                                  # shown in the Actions tab
run-name: CI for ${{ github.ref_name }} by @${{ github.actor }}

on:                                       # 1 · the events that start a run
  push:
    branches: [main]
  pull_request:
    paths: ['src/**', 'package*.json']
  workflow_dispatch:                      # adds a "Run workflow" button

permissions:                              # 2 · the GITHUB_TOKEN's scopes
  contents: read

concurrency:                              # 3 · one run per branch at a time
  group: ci-${{ github.ref }}
  cancel-in-progress: true

env:                                      # 4 · variables for every job
  NODE_ENV: test

jobs:
  test:                                   # 5 · a job: one runner, many steps
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v7         # an action
      - uses: actions/setup-node@v7
        with:
          node-version-file: .nvmrc
          cache: npm
      - run: npm ci                       # a script, in the default shell
      - run: npm test

  build:
    needs: test                           # 6 · waits for test to succeed
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - run: npm ci && npm run build
      - uses: actions/upload-artifact@v7
        with:
          name: dist
          path: dist/

The highlighted ${{ … }} spans are expressions: GitHub evaluates them before the text reaches the runner. Everything else in a run: line is handed to the shell as-is. Keeping that boundary in mind is half of the security skill — an expression that injects untrusted text into a script is the classic script-injection bug.

Two styles of pinning in these notes

First-party actions/* samples use major tags such as actions/checkout@v7 for readability. Third-party actions are pinned to a full commit SHA with the version in a trailing comment, as the security page recommends.

Where files live

PathWhat it holds
.github/workflows/*.ymlWorkflows, including reusable ones (on: workflow_call). Subdirectories are not scanned.
.github/workflows/actions.lockThe workflow lockfile that records the exact commit of every action a workflow uses (generally available since 28 October 2026)
action.yml / action.yamlAction metadata. At the repository root for a published action; Marketplace listing requires it there.
.github/actions/<name>/action.ymlA conventional home for a local action, used with uses: ./.github/actions/<name> after checkout
.github repository → workflow-templates/Organisation workflow templates: a .yml plus a matching .properties.json (and an optional icon)
.github/dependabot.ymlDependabot version updates, including package-ecosystem: github-actions to keep action versions current
.github/CODEOWNERSCode owners — commonly used to require review for /.github/workflows/

When each part is evaluated

Many questions reduce to timing. A workflow is not one script run top to bottom: different parts are read at different moments, by different components, and some contexts simply do not exist yet when a key is evaluated.

flowchart LR
  A["Event fires<br/>workflow file read<br/>from its commit"] --> B["Run queued<br/>repo and org<br/>secrets read"]
  B --> C["Job starts<br/>environment rules,<br/>environment secrets,<br/>GITHUB_TOKEN minted"]
  C --> D["Job ends<br/>token revoked"]
The life of a run. The workflow file comes from the commit the event points at — the default branch for schedule, workflow_dispatch defaults and pull_request_target. Repository and organisation secrets are read when the run is queued; environment secrets when a job that references the environment starts.
MomentWhat happensConsequence
Parse timeYAML parsed, anchors expanded, on: filters checkedInvalid YAML fails the run before any job starts, with an annotation on the run page
Run queuedrun-name, workflow-level concurrency, repo and org secretsrun-name can use github and inputs, not env
Job scheduledjobs.<id>.if, strategy, runs-on, job concurrencyThese can read needs, vars, inputs, matrix — but not env or steps
Job startsEnvironment rules, environment secrets, GITHUB_TOKEN minted, services startedA failed protection rule stops the job before any step runs
Each stepsteps[*].if, with, env, then the step runsOnly here can you read steps.<id>.outputs and values written to $GITHUB_ENV earlier

The Syntax reference has the full table of which contexts each key can see.

The Actions tab

The exam expects you to know where things are in the UI, not just in YAML. The Actions tab of a repository lists workflows on the left and their runs on the right, filterable by event, status, branch and actor. Opening a run shows its summary: the trigger, status, duration, a graph of jobs, annotations, job summaries and artifacts. Opening a job shows its steps and their logs.

StatusWhat it means
SuccessEvery job finished without a failure (a job that was skipped does not fail the run)
FailureA step exited non-zero and nothing caught it; downstream needs: jobs are skipped
SkippedA job's if: was false, or a job it needs did not succeed
CancelledCancelled by a person, the API, concurrency with cancel-in-progress, or a timeout
In progressRunning — or queued, waiting for a runner, a concurrency slot or an environment approval

A job's log is grouped by step. Here is the start of a checkout step as the log viewer shows it:

Run actions/checkout@v7
Run actions/checkout@v7
with:
repository: octo-org/web
token: ***
persist-credentials: true
Syncing repository: octo-org/web
Getting Git version info
Working directory is '/home/runner/work/web/web'
/usr/bin/git version
git version 2.51.0
Fetching the repository
Determining the checkout info
Notice: Checked out 1f2e3d4 (refs/heads/main)

Each step's output is folded into groups, secrets are replaced by ***, and annotations such as ::notice:: are lifted into the run summary. Skill 2 covers reading logs, re-running and downloading them in depth.

Runners in brief

RunnerWho manages itNotes
GitHub-hosted (standard)GitHubA fresh VM per job; Ubuntu, Windows and macOS; free for public repositories
Larger runnersGitHubMore CPU and memory, GPU, Arm, static IP ranges, Azure private networking, custom images; Team and Enterprise Cloud
Self-hostedYouYour own machine or container running the runner application; persistent unless ephemeral
Actions Runner ControllerYou, on KubernetesAutoscaling ephemeral self-hosted runners as runner scale sets

The current GitHub-hosted labels, checked on 5 November 2026: ubuntu-latest is moving from Ubuntu 24.04 to Ubuntu 26.04 in a gradual rollout between 19 October and 19 November 2026; windows-latest is Windows Server 2025; macos-latest is macOS 26 on Arm64. Skill 4 covers runner management in full.

Renames and retired syntax

Older Learn units, blog posts, forum answers and practice questions are full of names and commands that have moved. The exam follows the current product, but you will read both.

You may readCurrent name or syntaxNotes
Starter workflowsWorkflow templatesThe study guide still says "starter workflows"; the docs and the UI say workflow templates
echo "::set-output name=x::y"echo "x=y" >> "$GITHUB_OUTPUT"Retired; the runner now ignores the command
::save-stateecho "x=y" >> "$GITHUB_STATE"Retired alongside set-output
::set-env, ::add-path$GITHUB_ENV, $GITHUB_PATHDisabled since 2020 for security
actions/upload-artifact@v3actions/upload-artifact@v7, download-artifact@v8v3 stopped working in January 2025; v4+ artifacts are immutable and need matching majors
runs.using: node16 / node20runs.using: node24Node 24 became the default on 16 June 2026; Node 20 was removed on 23 September 2026, so older declarations now run on Node 24
ubuntu-20.04, windows-2019, macos-13ubuntu-24.04 / 26.04, windows-2025, macos-15 / 26Retired images fail to find a runner
"Required workflows" (organisation setting)Rulesets → Require workflows to pass before mergingRequired workflows moved into repository rulesets
Immutable actions (OCI packages on ghcr.io)Immutable releases and workflow lockfilesRemoved from GitHub's roadmap in December 2025; see Skill 3
pull_request_target running the base branch's workflowAlways the default branch's workflowChanged on 8 December 2025, along with how environment branch rules match PR events

Retired commands still look plausible

An answer option that uses ::set-output is wrong even though it looks like valid workflow-command syntax. If a question shows it in a log or script, the right reading is "this output will be empty". The same goes for actions/upload-artifact@v3, which fails today, and for node16 or node20 actions, which the runner now runs on Node 24 whatever they declare — untested, and liable to break.