GitHub Actions Fundamentals
Events, workflows, jobs, steps, actions — understand the core building blocks.
🧒 Simple Explanation (ELI5)
Imagine a giant domino chain. Someone pushes the first domino — that's an event (like pushing code to GitHub). The entire domino path laid out on the table is the workflow. Along the path, dominoes are grouped into sections separated by little bridges — those sections are jobs. Within each section, every single domino that falls is a step — they fall one after another in order.
Now here's the cool part: some of those dominoes aren't just plain tiles. They're fancy pre-built domino gadgets — little machines that spin, launch a ball, or ring a bell before tipping the next domino. Those gadgets are actions. Someone else built them, and you just plug them into your chain. You didn't have to engineer the spinning gadget yourself — you found it in the marketplace (a store full of pre-built gadgets) and dropped it into your path.
The full picture: You push the first domino (event). The chain (workflow) starts falling. Section 1 (Job 1) runs its dominoes one by one (steps). Some are plain tiles (shell commands), some are fancy gadgets (actions). Meanwhile, Section 2 (Job 2) on a separate table starts falling at the same time — jobs run in parallel by default. When all sections finish, the chain is complete and you get your result.
If any domino in a section fails to fall (a step errors out), the rest of that section stops. But the other sections on their separate tables keep going — unless you told them to wait.
🔧 Technical Explanation
Core Concepts Overview
GitHub Actions has six fundamental building blocks. Every workflow you write is composed of these elements:
| Concept | What It Is | Analogy |
|---|---|---|
| Event | A trigger that starts a workflow — a push, PR, schedule, manual button click, or external webhook | Pushing the first domino |
| Workflow | A YAML file in .github/workflows/ that defines the entire automation pipeline | The complete domino path blueprint |
| Job | A unit of work that runs on a single runner (virtual machine). Jobs run in parallel by default. | A section of dominoes on its own table |
| Step | A single task within a job — either a shell command (run:) or an action (uses:). Steps run sequentially. | An individual domino falling |
| Action | A reusable, pre-built unit of code that performs a specific task. Published on the GitHub Marketplace. | A fancy pre-built domino gadget |
| Runner | The server (virtual machine) that executes a job. GitHub-hosted (ubuntu, windows, macos) or self-hosted. | The table the dominoes sit on |
Events Deep Dive
Events are what trigger your workflow to start. GitHub supports dozens of event types. Here are the most important ones:
push — Fires when commits are pushed to a branch. The most common trigger for CI pipelines.
on:
push:
branches: [main, develop] # Only trigger on these branches
paths: ['src/**', 'tests/**'] # Only trigger when these files change
pull_request — Fires when a PR is opened, updated (pushed to), or synchronized. Essential for running checks before merge.
on:
pull_request:
branches: [main]
types: [opened, synchronize, reopened]
schedule — Cron-based trigger for running workflows on a schedule. Perfect for nightly builds, weekly reports, or periodic cleanup.
on:
schedule:
- cron: '0 2 * * *' # Every day at 2:00 AM UTC
workflow_dispatch — Adds a "Run workflow" button in the GitHub Actions UI. Allows manual triggering with optional custom inputs.
on:
workflow_dispatch:
inputs:
environment:
description: 'Target environment'
required: true
default: 'staging'
type: choice
options: [staging, production]
repository_dispatch — Triggered by an external system via the GitHub API. Used for integrating third-party tools or cross-repo orchestration.
on:
repository_dispatch:
types: [deploy-request] # Custom event type you define
release — Fires when a release is published, created, or edited. Common trigger for publishing packages or deploying production.
on:
release:
types: [published]
Workflows
A workflow is a YAML file stored in the .github/workflows/ directory of your repository. Each file defines one complete automation pipeline. Key rules:
- The filename can be anything ending in
.ymlor.yaml(e.g.,ci.yml,deploy.yml) - A repository can have multiple workflows — they run independently
- Workflows must have at least one trigger (
on:) and at least one job - Workflows are version-controlled — changes are reviewed in PRs like any other code
- The
name:field is optional but strongly recommended — it appears in the Actions UI
Jobs — Parallel by Default
Jobs define what to run and where to run it. Each job gets its own fresh runner (virtual machine). Critical behavior:
- Parallel by default: If you define
buildandtestjobs, they start at the same time on separate runners - Dependencies with
needs: Use theneedskeyword to create sequential execution.deploywithneeds: [build, test]waits for both to complete - Isolated environments: Each job starts fresh — no files, no environment variables, no state from other jobs. Use artifacts to share data between jobs.
- Conditionals with
if: Jobs can be skipped based on expressions, e.g.,if: github.ref == 'refs/heads/main'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build
test:
runs-on: ubuntu-latest
needs: build # Waits for build to finish first
steps:
- uses: actions/checkout@v4
- run: npm test
deploy:
runs-on: ubuntu-latest
needs: [build, test] # Waits for BOTH build and test
if: github.ref == 'refs/heads/main'
steps:
- run: echo "Deploying to production..."
Steps — Sequential Within a Job
Steps are the individual tasks within a job. They execute sequentially — step 2 waits for step 1 to finish. Each step is either:
run:— Execute a shell command directly. This is your own custom code.uses:— Execute a pre-built action from the GitHub Marketplace or your own repository.
Steps share the same runner filesystem, so files created in step 1 are available in step 2. They also share environment variables set with $GITHUB_ENV.
Actions — Reusable Building Blocks
Actions are pre-built, reusable units that perform specific tasks. Instead of writing 50 lines of shell commands, you reference an action with one line. The most commonly used actions:
| Action | What It Does | Usage |
|---|---|---|
| actions/checkout@v4 | Clones your repository into the runner | Required in almost every job |
| actions/setup-node@v4 | Installs a specific Node.js version | Node.js projects |
| actions/setup-python@v5 | Installs a specific Python version | Python projects |
| actions/cache@v4 | Caches dependencies between runs | Speed up builds |
| actions/upload-artifact@v4 | Saves files from a job for later use | Share build output between jobs |
| actions/download-artifact@v4 | Downloads artifacts from previous jobs | Retrieve build output |
Actions are version-pinned using tags (e.g., @v4) or commit SHAs for maximum security (e.g., @a5ac7e51b41094c92402da3b24376905380afc29).
GitHub Context Objects
GitHub provides context objects accessible in workflow expressions using the ${{ }} syntax. These give you metadata about the event, repository, and runner:
| Context | Value | Example Use |
|---|---|---|
github.sha | The commit SHA that triggered the workflow | Tagging Docker images: myapp:${{ github.sha }} |
github.ref | The branch or tag ref (e.g., refs/heads/main) | Conditional deploys: if: github.ref == 'refs/heads/main' |
github.actor | The username who triggered the workflow | Notifications: "Deployed by ${{ github.actor }}" |
github.event_name | The event type (push, pull_request, schedule, etc.) | Conditional logic based on trigger type |
github.repository | Owner and repo name (e.g., octocat/my-app) | Building registry paths |
github.run_id | Unique numeric ID for the current workflow run | Linking to logs |
github.workspace | The working directory on the runner | File path references |
📊 GitHub Actions Architecture
💻 Code Example — Complete Workflow Explained
Here's a complete CI workflow with every single line annotated:
# name: Human-readable name for this workflow.
# Appears in the Actions tab of your repository.
name: CI Pipeline
# on: Defines which events trigger this workflow.
# This workflow runs on two events: push and pull_request.
on:
# push: Triggered when commits are pushed to the repository.
push:
# branches: Only trigger on pushes to the 'main' branch.
# Pushes to feature branches are ignored.
branches: [main]
# pull_request: Triggered when a PR is opened or updated.
pull_request:
# branches: Only trigger for PRs targeting the 'main' branch.
# PRs targeting other branches are ignored.
branches: [main]
# jobs: Defines all the jobs in this workflow.
# Each job runs on its own virtual machine (runner).
jobs:
# 'build' is the job ID — used by other jobs to reference this one.
# You can name it anything: build, ci, lint, etc.
build:
# runs-on: Specifies the runner (virtual machine) for this job.
# 'ubuntu-latest' gives you a fresh Ubuntu VM with common tools
# pre-installed (git, node, python, docker, etc.).
runs-on: ubuntu-latest
# steps: The sequential list of tasks this job performs.
# Each step runs in order. If one fails, the rest are skipped.
steps:
# uses: Run a pre-built action from the GitHub Marketplace.
# actions/checkout@v4 clones your repository code onto the runner.
# Without this, the runner has no access to your code.
- uses: actions/checkout@v4
# name: Human-readable label for this step (appears in logs).
# uses: actions/setup-node@v4 installs Node.js on the runner.
- name: Setup Node.js
uses: actions/setup-node@v4
# with: Passes input parameters to the action.
# node-version: '20' installs Node.js version 20.x.
with:
node-version: '20'
# run: Executes a shell command directly on the runner.
# 'npm ci' installs dependencies from package-lock.json.
# It's faster and stricter than 'npm install' — preferred in CI
# because it ensures exact versions from the lockfile.
- run: npm ci
# run: Another shell command — runs the project's test suite.
# If any test fails, this step fails, the job fails,
# and the workflow reports failure on the PR.
- run: npm test
Every uses: step references a pre-built action (someone else's code). Every run: step executes your own shell commands. You'll use a mix of both in every real workflow. Think of uses: as importing a library and run: as writing your own function.
📋 Event Types Reference
Here are the most commonly used GitHub Actions events, when they fire, and when to use each:
| Event | Trigger Type | Example Config | When to Use |
|---|---|---|---|
| push | Automatic — on git push | on: push: branches: [main] |
Run CI on every commit to a branch. Most common trigger for build + test pipelines. |
| pull_request | Automatic — on PR activity | on: pull_request: branches: [main] |
Run checks before code is merged. Pairs with branch protection to block bad PRs. |
| schedule | Cron-based timer | on: schedule: - cron: '0 2 * * 1' |
Nightly builds, weekly dependency updates, periodic vulnerability scans, stale issue cleanup. |
| workflow_dispatch | Manual — button in UI | on: workflow_dispatch: inputs: env: … |
Manual deployments, ad-hoc tasks, on-demand report generation. Supports custom inputs. |
| release | Automatic — on release publish | on: release: types: [published] |
Publish packages to npm/PyPI, deploy production, build release binaries, create changelogs. |
| issues | Automatic — on issue activity | on: issues: types: [opened, labeled] |
Auto-assign reviewers, add labels, post welcome messages, triage new issues. |
You can combine multiple events in a single workflow. A workflow with on: [push, pull_request] runs on both — but be careful, a push to a branch that has an open PR will trigger both events, resulting in duplicate runs. Use branch and path filters to avoid this.
⌨️ Hands-on: Build a Multi-Event Workflow
Let's create a workflow that triggers on both push and pull_request, uses the actions/checkout action, and runs a custom step.
Step 1: Create the Workflow File
In your repository, create .github/workflows/fundamentals-lab.yml:
name: Fundamentals Lab
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
explore:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Show GitHub context
run: |
echo "Event: ${{ github.event_name }}"
echo "Branch: ${{ github.ref }}"
echo "Commit SHA: ${{ github.sha }}"
echo "Actor: ${{ github.actor }}"
echo "Repository: ${{ github.repository }}"
echo "Run ID: ${{ github.run_id }}"
- name: List repository files
run: |
echo "--- Repository root ---"
ls -la
echo ""
echo "--- Git log (last 3 commits) ---"
git log --oneline -3
- name: Custom build step
run: |
echo "Building the project..."
echo "In a real workflow, this would be: npm ci && npm run build"
echo "Build complete! ✅"
Step 2: Commit and Push
mkdir -p .github/workflows # paste the YAML above into .github/workflows/fundamentals-lab.yml git add .github/workflows/fundamentals-lab.yml git commit -m "ci: add fundamentals lab workflow" git push origin main
Step 3: View the Workflow Run
Go to your repository on GitHub and click the Actions tab. You'll see "Fundamentals Lab" in the list. Click into the run to see:
- The event that triggered it — "push" in this case
- The job name — "explore"
- Each step's output — expand "Show GitHub context" to see all the context variables printed
- Timing — how long each step took
Step 4: Test the Pull Request Trigger
Create a branch, make a change, and open a PR targeting main. The same workflow will trigger again — but this time github.event_name will show pull_request instead of push. This is how the same workflow adapts to different events.
Try adding workflow_dispatch: to the on: block (no extra config needed). After pushing, you'll see a "Run workflow" button appear in the Actions tab. Click it to manually trigger the workflow — the context will show workflow_dispatch as the event name.
🐛 Debugging Common Issues
Scenario: "Workflow Never Triggers"
You pushed code but nothing shows up in the Actions tab. Here's the systematic checklist:
- Check the file path: The workflow YAML must be in
.github/workflows/— exactly. Not.github/workflow/(no "s"), notgithub/workflows/(missing dot), not at the repo root. The directory name is case-sensitive on Linux runners. - Check the branch filter: If your workflow says
on: push: branches: [main]but you pushed tofeature/my-branch, it won't trigger. Remove the branch filter temporarily to verify:on: [push]. - Check YAML syntax: A single indentation error can silently break the file. Use a YAML linter or the GitHub workflow editor (which has built-in validation). Common mistake: using tabs instead of spaces.
- Check file extension: The file must end in
.ymlor.yaml. A.txtor no extension won't work. - Check Actions permissions: Go to Settings → Actions → General. Ensure "Allow all actions and reusable workflows" is selected (or at least "Allow actions created by GitHub").
- Check the branch where the YAML exists: For
pushevents, the workflow file must exist on the branch you're pushing to. If you added the file onmainbut pushed todevelop, and the file doesn't exist ondevelop, it won't trigger.
Scenario: "Job Shows 'Skipped'"
The workflow triggered but one or more jobs show "Skipped" with a grey icon:
- Check the
if:conditional: The job has anif:expression that evaluated tofalse. For example,if: github.ref == 'refs/heads/main'skips the job on any other branch. Click the skipped job to see the condition that was evaluated. - Check
needs:dependencies: If a job usesneeds: buildand thebuildjob failed, dependent jobs are skipped by default. To run a job even when dependencies fail, addif: always(). - Check event type: A
pull_requestevent won't havegithub.ref == 'refs/heads/main'— it has the PR's head ref. Conditions written for push events may not work on pull_request events. - Check path filters: If your workflow uses
on: push: paths: ['src/**']and you only changedREADME.md, the workflow may trigger but with all jobs skipped because no relevant files changed.
Enable debug logging by adding the secret ACTIONS_STEP_DEBUG with value true in your repository settings (Settings → Secrets and variables → Actions). This reveals verbose step-level output including action internals, which is invaluable for troubleshooting.
🎯 Interview Questions
Beginner
A GitHub Action is a reusable, pre-built unit of automation that performs a specific task within a workflow. Actions are referenced using the uses: keyword (e.g., uses: actions/checkout@v4). They can be published on the GitHub Marketplace for anyone to use, or stored privately in your own repository. Actions abstract away complex logic — instead of writing 50 lines of shell commands to set up a Node.js environment, you use actions/setup-node@v4 with one line. Actions are versioned (using Git tags like @v4 or commit SHAs) so you can pin to a specific, known-good version.
In GitHub Actions, the terms are essentially interchangeable. An event is something that happens in or to your repository — a push, a pull request being opened, a release being published, a cron schedule firing, or an external API call. The event triggers the workflow to run. The on: key in the workflow YAML defines which events trigger it. However, events can have sub-filters (branches, paths, types) that narrow when the trigger actually fires. So an event is the "thing that happened" and the trigger is "the condition that starts the workflow" — practically, they're the same concept in GitHub Actions.
runs-on: ubuntu-latest mean?runs-on specifies the runner — the virtual machine where the job executes. ubuntu-latest means GitHub will provision a fresh Ubuntu Linux VM (currently Ubuntu 22.04 or 24.04, depending on updates) with common development tools pre-installed (Git, Docker, Node.js, Python, Go, .NET, etc.). After the job completes, the VM is destroyed — every run starts from a clean state. Other options include windows-latest, macos-latest, and self-hosted runners. You can also use specific versions like ubuntu-22.04 for reproducibility.
actions/checkout?Because the runner starts as a blank virtual machine with no access to your code. The actions/checkout@v4 action clones your repository into the runner's workspace ($GITHUB_WORKSPACE), making your source code, configuration files, and scripts available to subsequent steps. Without it, commands like npm install or dotnet build would fail because there's no project to build. It also sets up Git credentials so you can make commits from the workflow if needed. The only workflows that skip checkout are those that don't need repository code — e.g., workflows that only call external APIs.
uses: and run: in a step?run: executes a shell command directly — it's your own code running in bash (Linux/macOS) or PowerShell (Windows). Example: run: npm test. uses: executes a pre-built action — someone else's packaged code that performs a specific task. Example: uses: actions/checkout@v4. A step can have either run: or uses:, but not both. Think of run: as writing code inline and uses: as importing a library function. In practice, most workflows are a mix: a few uses: steps for setup, then run: steps for project-specific commands.
Intermediate
Jobs run in parallel by default. If you define three jobs — build, test, deploy — all three start simultaneously on separate runners. To create sequential execution, use the needs: keyword. For example, deploy: needs: [build, test] means the deploy job waits until both build and test complete successfully. If any dependency fails, dependent jobs are skipped (unless you add if: always()). This is powerful because you can mix parallel and sequential: build and lint in parallel, then test after both pass, then deploy after test passes. This pattern minimizes total pipeline time while maintaining correct ordering.
GitHub context objects provide metadata about the workflow run accessible via the ${{ }} expression syntax. The main contexts are: github.* (repo, event, SHA, ref, actor), env.* (environment variables), secrets.* (encrypted secrets), steps.* (outputs from previous steps), job.* (job status), runner.* (runner OS, temp directory), and matrix.* (current matrix values). Common uses: tagging Docker images with github.sha, conditional deployments with github.ref, referencing secrets with secrets.MY_TOKEN, and passing data between steps with steps.step_id.outputs.name. Context values are evaluated at runtime and injected into the YAML before execution.
workflow_dispatch and when would you use it?workflow_dispatch is an event that adds a "Run workflow" button to the Actions tab in GitHub. It allows manual triggering of workflows with optional custom inputs (text fields, dropdowns, booleans). Use cases: Manual deployments — trigger a production deploy on demand with an environment selector. Ad-hoc tasks — run a database migration, generate a report, or trigger a data sync. Testing — run a specific workflow without having to push a commit. Inputs are defined in the YAML and accessible via github.event.inputs.input_name. It's especially useful when you want automation but with human-initiated control — common in deployment workflows where you want to choose which environment to target.
Since jobs run on isolated runners, you use artifacts or job outputs. For files (build output, test reports, binaries): use actions/upload-artifact@v4 in the producing job and actions/download-artifact@v4 in the consuming job. For small values (strings, booleans): use job outputs — set an output in a step with echo "name=value" >> $GITHUB_OUTPUT, then reference it in another job with needs.job_id.outputs.name. Artifacts persist for the duration of the workflow run (default 90 days) and can be downloaded from the Actions UI. For large files, consider pushing to an external store (S3, Azure Blob, container registry) and passing the reference as a job output.
Third-party actions run code inside your CI environment with access to your secrets, source code, and GITHUB_TOKEN. Risks include: Supply chain attacks — a compromised action could exfiltrate secrets. Tag mutability — @v4 is a mutable Git tag; the maintainer could change what it points to. Unmaintained actions — abandoned actions may have unpatched vulnerabilities. Mitigations: Pin to commit SHA — uses: actions/checkout@a5ac7e51b41… is immutable. Fork critical actions — maintain your own copy of actions that access secrets. Use Dependabot — it can update action versions and alert on vulnerabilities. Restrict permissions — use permissions: at workflow or job level to limit what GITHUB_TOKEN can do. Review the source — check the action's repository for suspicious code before using it.
Scenario-Based
Create three separate workflow files for clarity and independent control. ci.yml: on: [push, pull_request] — runs lint and tests. Triggers on every branch. build.yml: on: push: branches: [main] — checks out code, builds Docker image, pushes to registry. Only runs when code is merged to main (not on feature branches). deploy.yml: on: workflow_dispatch: inputs: environment: … — accepts a target environment input, pulls the latest image, deploys to the chosen environment. This separation follows the principle of single responsibility: CI runs on all branches, build runs on main, and deploy is human-initiated. Alternatively, you can combine all three in one file using job conditions: if: github.event_name == 'push' && github.ref == 'refs/heads/main' for build jobs, and if: github.event_name == 'workflow_dispatch' for deploy jobs.
The deploy job likely has if: always() set, which makes it run regardless of dependency status. Or, the deploy job is missing the needs: build keyword — without needs:, jobs run in parallel, so deploy starts immediately without waiting for build. Fix: Ensure deploy has needs: build (or needs: [build, test] if multiple dependencies). Remove if: always() unless you intentionally want it to run after failures (e.g., a cleanup or notification job). If you need a job that runs after failure but not on success, use if: failure() — this is useful for "notify team on failure" jobs.
@v4 (a tag). You suggest pinning to a commit SHA instead. Why?Git tags are mutable — the action maintainer can delete and recreate the v4 tag pointing to different code at any time. This means the code your workflow runs could change without any change to your workflow file. In a supply chain attack, a compromised maintainer could push malicious code to the same tag. A commit SHA (e.g., @a5ac7e51b41094c92402da3b24376905380afc29) is immutable — it always points to the exact same code. Pinning to SHA ensures: (1) reproducible builds, (2) protection against tag hijacking, (3) you know exactly what code runs in your CI. The tradeoff is readability and manual updates, which you mitigate by using Dependabot to automatically open PRs when new action versions are available.
github.ref isn't what you expect in a pull_request event?This is a common gotcha. On a push event, github.ref is the branch ref (e.g., refs/heads/main). But on a pull_request event, github.ref is the merge ref (e.g., refs/pull/42/merge), not the source branch. If your condition is if: github.ref == 'refs/heads/main', it will never match on PR events — causing jobs to be skipped. Fix: For PR events, use github.base_ref (the target branch, e.g., main) or github.head_ref (the source branch, e.g., feature/my-branch). Or use the event name: if: github.event_name == 'pull_request'. Debug step: Add a step that prints all context values: run: echo "${{ toJSON(github) }}" to see exactly what's available.
GitHub provides organizational controls at Settings → Actions → General: (1) Allow all actions — no restrictions. (2) Allow select actions — whitelist specific actions by owner (e.g., "actions/*", "docker/*") or individual actions. (3) Disable Actions — no workflows run at all. Best practice for enterprises: allow actions from GitHub (actions/*), allow verified marketplace creators, and maintain an internal allowlist of approved third-party actions. Additionally, require all actions be pinned to commit SHAs (enforced via a linting action like zgosalvez/github-actions-ensure-sha-pinned-actions). For maximum control, fork approved actions into an internal GitHub organization and require teams to use the internal forks — this way you control exactly what code runs in CI.
🌍 Real-World Use Case
A platform engineering team at a fintech company uses workflow_dispatch and schedule events to power two critical workflows:
Manual Deployments with workflow_dispatch
Their production deployment workflow uses workflow_dispatch with inputs for the target environment and the image tag. When a release manager is ready to deploy, they go to the Actions tab, select the deploy workflow, choose "production" from the environment dropdown, and paste the Docker image tag. The workflow then:
- Validates the image tag exists in the container registry
- Runs a preflight check against the target cluster (health, capacity)
- Deploys via Helm with the specified image tag
- Runs post-deploy smoke tests
- Sends a Slack notification with the deployment summary
This gives them full control over when deployments happen (no accidental deploys on merge) while keeping the process fully automated and auditable. Every deployment is logged in the Actions tab with who triggered it, what inputs were provided, and the full execution log.
Nightly Builds with schedule
Their scheduled workflow runs every night at 2 AM UTC. It:
- Builds all 8 microservices from the latest
mainbranch - Runs the full end-to-end test suite (too slow for PR-time CI)
- Scans all dependencies for known vulnerabilities (CVEs)
- Generates a coverage report and publishes it to an internal dashboard
- If any vulnerability is found, opens a GitHub Issue automatically and tags the security team
This combination — manual deploys for safety, nightly builds for thoroughness — gives them confidence that production is stable and that the codebase is continuously validated even when no one is actively committing.
📝 Summary
- Events trigger workflows — push, pull_request, schedule, workflow_dispatch, release, and 30+ more
- Workflows are YAML files in
.github/workflows/that define your automation pipeline - Jobs run in parallel by default on separate runners — use
needs:for sequential execution - Steps run sequentially within a job — either
run:(shell commands) oruses:(pre-built actions) - Actions are reusable building blocks from the GitHub Marketplace — always pin versions for safety
- Runners are the VMs that execute jobs — GitHub-hosted (ubuntu, windows, macos) or self-hosted
- GitHub Context provides metadata like
github.sha,github.ref,github.actorfor conditional logic and dynamic values - If a workflow doesn't trigger, check the file path, branch filter, YAML syntax, and Actions permissions
- If a job is skipped, check the
if:conditional andneeds:dependencies