Events Deep Dive
Every workflow starts with a question: “when should this run?” So far you’ve triggered workflows without thinking much about why they fired. Now it’s time to open that up. GitHub Actions supports dozens of event types, but four of them cover the vast majority of real-world automation: push, pull_request, workflow_dispatch, and schedule. Understanding what each one actually represents — not just its syntax — is what separates a workflow that runs “sort of when you want” from one that runs exactly when it should.
Why The Event Matters More Than It Looks
The event that triggers a workflow isn’t just a name in the YAML — it changes what data is available to you, what the runner checks out by default, and even what permissions the workflow gets. Pick the wrong trigger and you’ll either run jobs too often (wasting minutes and cluttering your checks) or miss the moment that actually matters (like a merge to production).
on: pushThis one line silently means “checkout will point at the commit that was just pushed” — different from pull_request, where checkout defaults to a merge commit that doesn’t exist as a permanent ref in your history. That distinction alone trips up a lot of people writing their first CI workflow.
The push Event
push fires whenever commits are pushed to any branch or tag in the repository (unless you filter it, which the next chapter covers). It also fires when you push tags.
name: On Push Demo
on: push
jobs:
announce:
runs-on: ubuntu-latest
steps:
- run: echo "A push happened on ref ${{ github.ref }}"Set this up yourself:
mkdir gha-events-demo && cd gha-events-demo
git init
mkdir -p .github/workflows
cat > .github/workflows/on-push.yml << 'EOF'
name: On Push Demo
on: push
jobs:
announce:
runs-on: ubuntu-latest
steps:
- run: echo "A push happened on ref ${{ github.ref }}"
EOF
git add .
git commit -m "Add on-push demo workflow"Note
Get familiar with cat > file << 'EOF' syntax. It writes everything provided between two EOFs into the file — try and get used to.
Push this to GitHub and watch the Actions tab — the job fires immediately, and the log line shows something like refs/heads/main.
Note
push also fires for force-pushes and for merge commits landing on a branch (since a merge is technically a push). It does not fire for pushes that only move a tag that already points at an existing commit with no new commits.
The pull_request Event
pull_request fires on PR lifecycle actions: opened, synchronized (new commits pushed to the PR branch), reopened, and a few others you can filter with types. This is your primary tool for pre-merge validation — linting, tests, security scans — before code ever touches your main branch.
name: On PR Demo
on: pull_request
jobs:
announce:
runs-on: ubuntu-latest
steps:
- run: echo "PR event. Head ref is ${{ github.head_ref }}, base ref is ${{ github.base_ref }}"The critical gotcha: for PRs from forks, pull_request runs with read-only permissions and no access to your repository’s secrets by default. This is a deliberate security boundary — GitHub doesn’t want a stranger’s fork triggering a workflow that leaks your AWS credentials. There’s a separate event, pull_request_target, that runs with write access and secrets available, but it checks out the base branch, not the fork’s code, specifically to prevent a malicious PR from smuggling in code that then runs with your permissions. Don’t reach for pull_request_target casually — it’s a common source of real security incidents when misused.
The workflow_dispatch Event
workflow_dispatch is a manual trigger — a button in the GitHub UI (or an API call) that a human or another system presses on purpose. No push, no PR, just “run this now.”
name: On Manual Demo
on: workflow_dispatch
jobs:
announce:
runs-on: ubuntu-latest
steps:
- run: echo "Manually triggered by ${{ github.actor }}"Push this, then go to the Actions tab, select the workflow in the left sidebar, and click “Run workflow.” You won’t see this option for push-only workflows — it only appears when workflow_dispatch is present.
The concept goes much deeper with input parameters, which get their own dedicated treatment shortly — for now, just know this event exists as your “break glass” trigger for things like manual deploys, one-off maintenance jobs, or re-running a data import outside its normal schedule.
The schedule Event
schedule runs a workflow on a timer, using cron syntax.
name: On Schedule Demo
on:
schedule:
- cron: '*/5 * * * *'
jobs:
announce:
runs-on: ubuntu-latest
steps:
- run: date -uThis fires roughly every 5 minutes. Cron syntax and its quirks (GitHub’s scheduler isn’t as precise as it looks) get a full chapter of their own next — for now, notice that schedule is the only one of these four events that isn’t triggered by a person or a code change. It’s triggered by the calendar.
Warning
Scheduled workflows only run on the default branch of your repository, using whatever workflow file exists there. If you edit a scheduled workflow on a feature branch, that change has no effect until it’s merged.
How These Combine In Practice
Real workflows rarely use one event alone. A typical pattern:
on:
push:
branches: [main]
pull_request:
workflow_dispatch:This says: run full CI on every PR (to catch problems before merge), run again on push to main (to confirm main itself is healthy, e.g. after a squash-merge), and allow a human to trigger it manually for debugging. This three-event combination is extremely common — you’ll see it in a large share of production repositories doing CI.
Comparing The Four Events
| Event | Fired By | Typical Use | Secrets on Forked PRs |
|---|---|---|---|
push | A commit lands on a branch/tag | Build, test on every change | N/A (not fork-based) |
pull_request | PR opened/updated | Pre-merge checks | Not available |
workflow_dispatch | A human clicks “Run workflow” | Manual deploys, one-off jobs | Available (it’s your repo) |
schedule | Cron timer | Nightly builds, cleanup jobs | Available (runs from default branch) |
How Real DevOps Engineers Use This
In production pipelines, engineers almost never rely on a single event in isolation. A common pattern for a deploy pipeline: pull_request runs tests and linting so nothing broken reaches main; push to main triggers the actual deployment, since that’s the signal that code has been reviewed and merged; workflow_dispatch exists as an escape hatch to redeploy a specific commit without pushing a new one (useful when a deploy fails for infrastructure reasons, not code reasons); and schedule handles maintenance tasks — dependency update checks, database backups, or cleaning up old container images — that have nothing to do with code changes at all.
Choosing the right event is a design decision, not a formality. Teams that trigger deployments on push to every branch often regret it once someone pushes to a scratch branch and accidentally deploys.
In case if you want to clean up the demo when you’re done:
cd .. && rm -rf gha-events-demoTip
If a workflow “isn’t running” and you’re stuck debugging, the first thing to check is almost always the event trigger — either the event never fired for that action, or a filter (covered next) excluded it.