Skip to content

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: push

This 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 -u

This 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

EventFired ByTypical UseSecrets on Forked PRs
pushA commit lands on a branch/tagBuild, test on every changeN/A (not fork-based)
pull_requestPR opened/updatedPre-merge checksNot available
workflow_dispatchA human clicks “Run workflow”Manual deploys, one-off jobsAvailable (it’s your repo)
scheduleCron timerNightly builds, cleanup jobsAvailable (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-demo

Tip

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.

Last updated on