Triggers & Events¶
A workflow does nothing until something tells it to run. That "something" is its trigger — the on:
key at the top of the workflow file — and the trigger you pick decides more than when the workflow fires:
it decides what the workflow is safe to be trusted with.
TL;DR — in 30 seconds:
- A workflow only runs when its
on:trigger fires —push,pull_request,schedule, orworkflow_dispatchare the four common ones, and a workflow file can list more than one. - Branch/tag and path filters narrow a trigger so it doesn't re-run the full pipeline on every push or every file change.
- A
pull_requestrun can come from a fork's untrusted content — the trigger you pick decides not just when a workflow runs but what it's safe to be trusted with.
1. The common trigger types¶
| Trigger | Fires on | Typical use |
|---|---|---|
push |
A commit lands on a branch (or tag) | Build/test on every change to your own branches |
pull_request |
A PR is opened/updated against a target branch | Check untrusted or in-review changes before merge |
schedule |
A cron expression, on a timer | Nightly builds, scheduled cleanup, periodic checks |
workflow_dispatch |
A human clicks "Run workflow" | Manual, on-demand runs (a release, a one-off job) |
flowchart LR
P["push"] --> W["Workflow run"]
PR["pull_request"] --> W
S["schedule (cron)"] --> W
M["workflow_dispatch (manual)"] --> W
A workflow file can list more than one trigger — it's common for the same workflow to run on both push
and pull_request, with the two paths behaving slightly differently (see §3).
2. Narrowing a trigger¶
Left unfiltered, on: push fires on every push to every branch. Two filters narrow that:
- Branch/tag filters — restrict the trigger to specific branches (e.g. only
main, or only release branches) or tags, so a workflow doesn't run on every scratch branch a developer pushes. - Path filters — restrict the trigger to pushes that touch specific files or directories, so a change to documentation doesn't re-run a build/test pipeline that only cares about application code.
Both exist for the same reason: running a full pipeline on every single push is wasteful and slows down everyone's feedback loop. Narrow the trigger to exactly the changes that matter.
3. Why the trigger changes what you can trust¶
push and pull_request look similar but carry a real difference: a pull_request can come from a
fork — someone else's copy of the repo, whose contents you don't control. A workflow triggered by a
pull request from a fork should not be handed the same secrets or write access a push to your own
branch gets, because that would let an untrusted contributor exfiltrate a secret just by opening a PR.
This is exactly why "which trigger" is a security question, not just a timing one — it's covered
operationally in Secrets & environments.
4. Manual and scheduled runs¶
workflow_dispatch exists for workflows a human should kick off deliberately — a production release, a
data-migration job — rather than have fire automatically on every push. schedule (using standard cron
syntax) exists for work that isn't tied to a code change at all: a nightly rebuild, a periodic dependency
check. Neither needs a code change to trigger; both still run the same jobs/steps machinery underneath.
5. How this connects¶
- Once a trigger fires, what happens next — how the workflow's jobs and steps actually execute — is covered in the next page, Jobs, steps & runners.
- The trust distinction in §3 is the reason Secrets & environments scopes secrets the way it does, and why
an
environment's protection rules matter most on the trigger that leads to a production deploy.
Common gotchas
- Treating a
pull_requesttrigger as equally trustworthy aspush. Fix: apull_requestcan come from a fork you don't control — never hand it the same secrets or write access apushto your own branch gets. - Leaving
on: pushunfiltered. Fix: without branch/tag or path filters, every push to every branch — even a documentation-only change — re-runs the full pipeline; narrow the trigger to the changes that matter. - Assuming
scheduleorworkflow_dispatchneeds an accompanying commit to fire. Fix: neither does — a scheduled run and a manual "Run workflow" click both start the same jobs/steps machinery with no code change involved. - Forgetting a workflow file can list more than one trigger. Fix: the same file commonly runs on
both
pushandpull_request, and each path can behave differently — check which trigger actually fired before assuming why a run started.
Check yourself — why shouldn't a workflow triggered by pull_request from a fork get the same secrets as one triggered by push to your own branch?
Fork content is untrusted — anyone can open a pull request from their own copy of the repo. If that
trigger got the same secrets/write access a push does, an attacker could exfiltrate a secret just by
opening a PR containing a workflow step that reads it.
Check yourself — a workflow re-runs on every single push, including documentation-only commits. What's the fix, and why not just delete the trigger?
Add a path filter to on: push so it only fires on pushes that touch the files the pipeline actually
cares about. Deleting the trigger entirely would also skip legitimate code changes bundled in the same
push as a doc edit — filtering narrows the trigger, it doesn't remove it.
You can defend this when you can pick the right trigger for a given scenario (routine build vs. PR
check vs. nightly job vs. manual release) and explain, unprompted, why a pull_request from a fork
shouldn't be trusted the same as a push to your own branch.