Study guide

8.2 Jobs, Steps, Runners, Secrets, and Reusable Actions

GitHub Foundations GH-900

Track your progress as you read

Finished sections get a check mark.

1 Introduction & Exam Overview

1.1 Current Exam Facts 1.2 Domains, Scoring, and Candidate Profile 1.3 Official Resources and Hands-On Prep

2 Version Control Fundamentals

2.1 Purpose and Benefits of Version Control 2.2 Git vs GitHub 2.3 Repositories, Commits, and History 2.4 Branches, HEAD, and the Default Branch

3 Working with GitHub

3.1 Accounts, Organizations, and Enterprise Options 3.2 GitHub Flow 3.3 Markdown for Issues and Pull Requests 3.4 GitHub Desktop and GitHub Mobile

4 Repository Structure and Key Files

4.1 Repository Structure and Community Health Files 4.2 Creating Repositories with Templates and Branches 4.3 Adding and Managing Files

5 Repository Insights and Maintenance

5.1 Insights, Stars, Dashboards, and Dependency Insights 5.2 Repository Maintenance and Collaboration Best Practices

6 Issues and Pull Requests

6.1 Issues 6.2 Pull Requests 6.3 Linking PRs, Templates, Filters, and Assignments

7 Discussions, Notifications, and Documentation

7.1 Discussions 7.2 Notifications and Workflow Management 7.3 Gists, Wikis, and GitHub Pages

8 GitHub Actions

8.1 Actions Purpose, Workflows, and Events 8.2 Jobs, Steps, Runners, Secrets, and Reusable Actions

9 GitHub Copilot

9.1 Copilot Suggestions, Agents, Agent Mode, and Multi-Model Support 9.2 Copilot for Individuals, Business, and Enterprise

10 Codespaces and github.dev

10.1 Codespaces, Customization, and Dev Containers 10.2 github.dev vs Codespaces

11 Manage Projects with GitHub

11.1 GitHub Projects and Layout Options 11.2 Configuring Projects, Labels, Milestones, and Workflows 11.3 Saved Replies and Assignees 11.4 Project Insights

12 Account Security and Access

12.1 Two-Factor Authentication and Passkeys 12.2 Access Permissions and Roles 12.3 Enterprise Managed Users and Copilot Policy

13 Privacy, Branch Protection, and Organization Admin

13.1 Privacy, Visibility, and Branch Protection 13.2 Organization Settings, Teams, and Roles

14 Open Source, Sponsors, and InnerSource

14.1 Open Source Benefits and GitHub Sponsors 14.2 How GitHub Supports Open-Source Projects 14.3 InnerSource

15 Marketplace, Discovery, and Collaboration Models

15.1 Following Users and Organizations 15.2 GitHub Marketplace 15.3 Forks, Templates, and Discoverable Repositories

8.2 Jobs, Steps, Runners, Secrets, and Reusable Actions

Key Takeaways Explain This Topic AI

Jobs in a workflow run in parallel by default; add a needs key when a later job must wait for another job to succeed.

Each job runs on a runs-on runner; steps either run a script or uses a reusable action, with optional with inputs and env variables.

GitHub-hosted runners are a fresh Ubuntu, Windows, or macOS virtual machine for every job; self-hosted runners persist on hardware you maintain and do not need a clean instance per job.

Store secrets at repository, organization, or environment scope and never echo them into logs; GitHub injects GITHUB_TOKEN automatically for each job.

Included minutes for standard hosted runners on private repositories are 2,000 (Free), 3,000 (Pro and Team), and 50,000 (Enterprise Cloud) per month; public repositories using standard GitHub-hosted runners do not consume that quota.

Last updated: August 2026

Jobs, steps, and the YAML keys GH-900 expects

Section 8.1 put an event in front of a workflow file. This section opens the file. GitHub's component model is small enough to memorize: a workflow contains jobs ; a job contains steps ; a step either runs a script or runs an action ; the job executes on a runner .

A job is a set of steps that execute on the same runner . Because they share that machine, step two can see files step one created: checkout, then install, then test. Steps run in order inside a job. Jobs run in parallel by default. If you declare test and lint with no relationship, GitHub starts both as soon as runners are free. That surprises people who read YAML top-to-bottom and assume sequence. To force sequence, set needs to the job id that must finish first: jobs:

lint:

runs-on: ubuntu-latest

steps:

  • uses: actions/checkout@v4
  • run: npm run lint

test:

runs-on: ubuntu-latest

steps:

  • uses: actions/checkout@v4
  • run: npm test

package:

  • needs: [lint, test]
  • runs-on: ubuntu-latest

steps:

  • uses: actions/checkout@v4
  • run: npm pack

lint and test still overlap. package waits until both succeed. If lint fails, package is skipped. Jobs do not share a filesystem: each job is a different runner (or at least a different job slot), so you pass files with artifacts if a later job needs a build output. GH-900 may not require artifact syntax, but it will punish the myth that job two can cd into job one's checkout.

A step is either:

run: — a shell script on the runner ( bash on Linux and macOS, PowerShell on Windows unless you choose otherwise).

uses: — a reusable action , which is packaged code that performs a common task (checkout the repo, set up Node.js, upload a release).

uses often takes with: inputs. env: sets environment variables for a step or a whole job. Map those words to YAML keys you will see on the exam:

  • YAML key Where it sits Meaning
  • name Workflow (or job/step) Display title in the Actions tab
  • on Workflow Events that start a run
  • jobs Workflow Map of job ids to job definitions
  • runs-on Job Which runner label to request
  • needs Job Other job ids that must complete first
  • steps Job Ordered list of run or uses
  • uses Step Reusable action ( owner/repo@ref or a local path)
  • with Step Inputs passed to that action
  • env Workflow, job, or step Environment variables
  • run Step Inline script

The following teaching workflow uses those keys together—including a secret and a sequenced deploy—without turning into a Marketplace dump: name: Build and deploy

on:

push:

branches: [main]

workflow_dispatch:

jobs:

test:

runs-on: ubuntu-latest

steps:

  • uses: actions/checkout@v4
  • uses: actions/setup-node@v4

with:

  • node-version: "20"
  • cache: npm
  • name: Install and test
  • run: |
  • npm ci
  • npm test

env:

NODE_ENV: test

deploy:

  • needs: test
  • runs-on: ubuntu-latest
  • environment: production

steps:

  • uses: actions/checkout@v4
  • name: Deploy
  • run: ./scripts/deploy.sh

env:

DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

Read it the way the exam will: on is push-to-main or a manual run; test and deploy are jobs; deploy needs test ; both runs-on Ubuntu; steps mix uses (checkout, setup-node) with run ; with configures Node 20; env passes NODE_ENV and a mapped secret. ${{ }} is GitHub's expression syntax; secrets.DEPLOY_TOKEN is not written in the YAML as the real token.

Loading diagram... Jobs run in parallel until needs creates a sequence

Runners: GitHub-hosted versus self-hosted

A runner is the server that executes a job. Each runner runs one job at a time . You pick it with runs-on .

GitHub-hosted runners are virtual machines GitHub provides. Standard images cover Ubuntu Linux , Windows , and macOS ( ubuntu-latest , windows-latest , macos-latest ). GitHub maintains the image (tools update weekly) and, for typical hosted VMs, each job gets a fresh, newly provisioned virtual machine . When the job ends, that VM is gone. You cannot SSH in later and find yesterday's node_modules . That isolation is the security and reproducibility story: job B cannot read job A's leftover credentials off disk.

Self-hosted runners are machines you deploy—physical, virtual, container, on-premises, or in your cloud. You install GitHub's runner application and register the machine at repository, organization, or enterprise scope. GitHub's docs are explicit: self-hosted runners do not need a clean instance for every job . They persist. That is the feature (cached tools, GPU hardware, access to a private network) and the risk (leftover files, long-lived secrets on disk). You patch the OS. GitHub auto-updates the runner application unless you disable that. Self-hosted use of GitHub Actions is not billed as hosted minutes ; you pay for the hardware. Standard hosted minutes still apply when the same workflow uses ubuntu-latest instead.

  • GitHub-hosted Self-hosted
  • Who owns the machine GitHub You
  • Operating systems Ubuntu, Windows, macOS images Whatever you install
  • Freshness New VM (or equivalent) per job Persistent; jobs can see prior state
  • Maintenance GitHub images and capacity You patch OS, tools, and capacity
  • Typical runs-on ubuntu-latest A label you assigned, for example [self-hosted, linux, x64]

Minutes Consume plan quota on private repos for standard hosted runners You pay infrastructure; not the hosted-minute SKU

Larger hosted runners (more CPU, GPU, custom images) exist on Team and Enterprise Cloud. They are always billed , including on public repositories. If a question says "public repo, standard ubuntu-latest ," minutes are free. If it says "public repo, 64-core larger runner," it is not free. Do not flatten those two.

Secrets, GITHUB_TOKEN, and reusable actions

Secrets are encrypted variables for sensitive values: deploy tokens, package registries, cloud keys. You create them at three scopes:

Repository secrets — one repo.

Organization secrets — many repos, optionally limited to selected repositories.

Environment secrets — tied to a GitHub environment ( production , staging ). You can require reviewers before a job that references that environment starts, so a production secret never reaches an unapproved run.

If the same name exists at more than one level, environment wins , then repository, then organization. Reference a secret as ${{ secrets.NAME }} and pass it through env or with . Never echo secrets. Do not echo ${{ secrets.DEPLOY_TOKEN }} , do not print them in curl -v , and do not write them to a public summary. GitHub redacts secrets that it knows about from logs, but redaction is not a license to print them, and obfuscation tricks can leak values GitHub does not recognize.

GITHUB_TOKEN is different from secrets you create. At the start of each job GitHub automatically creates a unique GITHUB_TOKEN (a GitHub App installation access token for that repository) and expires it when the job ends. You do not paste it in Settings. Use ${{ secrets.GITHUB_TOKEN }} (or github.token ) to checkout extra refs, comment on a pull request, or open an issue from the workflow. Permissions are limited by repository/org defaults; least privilege is the secure default. Fork pull requests do not receive repository, organization, or environment secrets. They may use GITHUB_TOKEN with read-only permission. That is why "a first-time contributor's PR deploys with our cloud key" is a wrong mental model—GitHub withholds those secrets on purpose.

Reusable actions keep workflow YAML short. GitHub Marketplace lists actions you can uses: by owner/repo@ref , commonly actions/checkout@v4 or actions/setup-node@v4 . You can also write a custom action in your repository ( uses: ./path/to/action ) or in another repository you control. Marketplace is convenience and community review; custom is private logic and org standards. Neither replaces the workflow file. A reusable workflow ( workflow_call ) is a further pattern: one YAML file that other workflows call. GH-900 needs the idea that you can reuse; it does not need you to author action.yml from scratch.

Included minutes

GitHub Actions billing states that standard GitHub-hosted runners are free for public repositories (and for GitHub Pages and Dependabot). Private repositories consume a monthly minute quota that resets each billing cycle, charged to the repository owner :

  • Plan Included minutes / month
  • GitHub Free (personal and Free for organizations) 2,000
  • GitHub Pro 3,000
  • GitHub Team 3,000
  • GitHub Enterprise Cloud 50,000

Those numbers are for standard hosted runners. Windows and macOS hosted jobs cost more per minute than Linux if you exceed the quota; do not memorize every SKU for GH-900, but do not claim they are identical either. Artifact storage is a separate allowance (and is shared with GitHub Packages). If the account has no payment method, usage stops when the quota is gone.

Exam scenarios and traps

Two jobs with no needs → parallel, two runners, no shared disk.

"Deploy only after tests pass" → needs: test on the deploy job, not a comment in the YAML.

Fresh VM per GitHub-hosted job vs persistent self-hosted machine is the runner discriminator. "We need last week's cached SDK on the same box" points at self-hosted. "We need a clean Ubuntu every run" points at hosted.

Secrets belong in Settings (repo, org, or environment), not committed as env: TOKEN: supersecret in YAML.

Never echo. GITHUB_TOKEN is automatic; you still should not print it.

Fork PRs do not get your cloud secrets.

Free = 2,000; Pro and Team = 3,000; Enterprise Cloud = 50,000. Public + standard hosted = free. Public + larger runner = paid.

uses + with is an action. run + env is a script. Both are steps.

If a GH-900 item mentions YAML, map the key ( on , jobs , runs-on , steps , uses , with , env , needs ) before you invent a product. If it mentions money, start from public-versus-private and standard-versus-larger, then apply 2,000 / 3,000 / 50,000.

Included GitHub Actions minutes per month (private repos, standard hosted runners)