A pipeline that runs at the wrong time is worse than no pipeline. In this lesson you'll master the on: block — the gatekeeper that decides when your automation fires.
The Mental Model: Events → Workflows
GitHub constantly emits events as things happen in your repository. Your workflow's on: block subscribes to specific events — like a webhook listener.
on: filter trigger the workflow. Everything else is ignored.on: as a subscription filter. GitHub emits hundreds of event types — your workflow only wakes up for the ones you explicitly subscribe to.
The Trigger Categories
All triggers fall into four categories. As an architect, you'll use all four:
Push & Pull Request: The Workhorses
90% of your triggers will be push and pull_request. Here's the crucial difference:
Branch & Path Filtering
Raw triggers are too broad. Filters narrow them to exactly what you need:
Branch Filters
on:
push:
branches:
- main # exact match
- 'release/**' # glob: release/1.0, release/2.0, etc.
branches-ignore:
- 'dependabot/**' # skip auto-update branches
Path Filters (Critical for Mono-Repos)
on:
push:
paths:
- 'src/**' # Only when source code changes
- 'package-lock.json' # Or when deps change
paths-ignore:
- '**.md' # Never run for docs changes
- 'docs/**'
Tag Triggers (Releases)
on:
push:
tags:
- 'v*' # v1.0.0, v2.3.1, v0.1.0-rc.1
- '!v*-rc*' # exclude release candidates
This is how release pipelines work: you tag a commit → pipeline builds and publishes the release. We'll build this in a later lesson.
Schedule (Cron Syntax)
on:
schedule:
- cron: '0 2 * * 1' # Every Monday at 2:00 AM UTC
Manual Trigger: workflow_dispatch
This adds a "Run workflow" button in the GitHub UI. Essential for operational tasks:
on:
workflow_dispatch:
inputs:
environment:
description: 'Target environment'
required: true
type: choice
options:
- staging
- production
reason:
description: 'Why are you deploying?'
required: true
type: string
Access inputs in your workflow with ${{ inputs.environment }}
🏋️ Exercise: Multi-Trigger Workflow
Update your ci.yml to use multiple triggers with filters:
name: CI
on:
push:
branches: [main]
paths-ignore:
- '**.md'
pull_request:
branches: [main]
workflow_dispatch: # manual trigger too
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm test
- name: Show trigger info
run: |
echo "Triggered by: ${{ github.event_name }}"
echo "Branch: ${{ github.ref_name }}"
Test it three ways:
- Push to main → watch it trigger
- Edit only
README.mdand push → it should NOT trigger (paths-ignore) - Go to Actions tab → click "Run workflow" → it runs manually
Decision Framework: Which Trigger When?
| Scenario | Trigger | Why |
|---|---|---|
| Validate PR before merge | pull_request | Gatekeeper — catch bugs before main |
| Deploy after merge | push: branches: [main] | Only deploy validated code |
| Create a release | push: tags: ['v*'] | Version tag = release intent |
| Nightly security scan | schedule: cron | Catch new CVEs even without code changes |
| Emergency rollback | workflow_dispatch | Human-initiated, parameterised |
| Mono-repo: only changed service | push: paths: ['svc-a/**'] | Don't waste time rebuilding unchanged services |
🧠 Recall Check
- What's the difference between
on: pushandon: pull_requestin terms of when they fire? - You change only
README.md. Your workflow haspaths-ignore: ['**.md']. Does it run? - How do you make a workflow runnable manually from the GitHub UI?
- What trigger would you use for a "scan for vulnerabilities every night" workflow?
Reveal answers
pushfires when commits land on a branch (after merge/direct push).pull_requestfires when a PR is opened, updated, or reopened (before merge).- No. The only changed file matches
paths-ignore, so the workflow is skipped. - Add
workflow_dispatch:to theon:block. Optionally defineinputs:for parameters. schedulewith a cron expression, e.g.,cron: '0 2 * * *'for 2 AM daily.