Lesson 3 of 6

Triggers & Events

The on: key decides when your workflow runs — and choosing well is the difference between CI that guards every change, and a noise machine burning minutes on runs nobody asked for. Four triggers cover nearly everything.

💡 The Bottom Line

push guards branches, pull_request guards merges, schedule runs on a cron timetable, and workflow_dispatch adds a manual "Run workflow" button. Branch and path filters keep each one scoped to changes that actually matter.

The big four

on:
  push:
    branches: [main]          # commits landing on main
  pull_request:
    branches: [main]          # PRs *targeting* main
  schedule:
    - cron: "0 6 * * 1"       # Mondays 06:00 UTC
  workflow_dispatch:          # manual button (+ optional inputs)

Filters: run only when it matters

on:
  push:
    branches: [main, "release/**"]
    paths:
      - "src/**"
      - "package.json"

branches limits which branch's pushes count (glob patterns allowed); paths skips the run entirely when only unrelated files changed — a docs-only commit doesn't need the full test suite. In a monorepo, paths filters per-project workflows down to the project that actually changed; combined with the pipeline speed concerns from the CI/CD track, these two filters are your minute budget's best friends.

🙋 There Are No Dumb Questions

Q: Push and pull_request both fire on my PR branch — am I paying for everything twice?
A: Quite possibly, and it's the most common accidental double-spend. A push to a PR branch fires push (if unfiltered) and updates the PR (firing pull_request). Standard fix: filter push to branches: [main] only, and let pull_request own everything pre-merge — each commit then triggers exactly one run, in the right context.

⚠️ Watch Out

Two schedule traps: times are UTC (your 6 a.m. cron may fire at your 11:30 a.m. — or during your maintenance window), and scheduled workflows run on the default branch's copy of the file — your cron edits on a feature branch do nothing until merged. Also, GitHub disables schedules on repos with no activity for 60 days — a surprise for that quarterly job.

🔧 Try It Yourself

Add a manual trigger with an input to your scratch workflow:

on:
  workflow_dispatch:
    inputs:
      who:
        description: "Greet whom?"
        default: "world"
jobs:
  greet:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Hello, ${{ github.event.inputs.who }}!"

Push to the default branch, open Actions → your workflow → Run workflow, type a name, run it. That ${{ ... }} syntax is the expression language — the same one that reads secrets in the next lesson.

✅ Quick Check

What does the pull_request trigger test that plain push doesn't?


Next up: your workflow needs credentials to do anything interesting — secrets and variables, done safely.

← Back to
Next →