Chapter 03Lesson 04~130 minutes

Events, Triggers, Filters, Schedules, Manual Runs, and Repository Dispatch: Diagnostics, Failure Modes, and Production Practices

A trigger failure is often misdiagnosed because people look only at YAML and the latest Actions page. This lesson uses a causal sequence: preserve what happened, determine whether an event existed, identify the selected ref/SHA and workflow revision, prove filter evaluation, and only then inspect jobs/runners if a run was actually created.

DiagnosticsNo-run failuresPR SHASchedule delayTrust boundary

Learning objectives

  • Diagnose “no run,” “wrong run,” and “run but skipped job” as separate failure classes.
  • Explain why branch and path filters combine with AND semantics when both are configured.
  • Diagnose wrong pull-request SHA assumptions using merge-ref, head-ref, and payload evidence.
  • Recognize schedule delay/default-branch behavior without treating it as runner failure.
  • Repair an intentionally broken trigger with the smallest change while preserving first-failure evidence.

1. Evidence-first trigger diagnostic sequence

  1. Preserve: event/commit/PR/API request evidence and any existing run ID/attempt.
  2. Confirm event: did the repository activity or API request actually happen?
  3. Confirm workflow eligibility: did the workflow exist at the required ref/default branch?
  4. Evaluate filters: activity types, branches/tags, paths, commit-message skip rules.
  5. Confirm ref/SHA: especially for PR merge context versus head context.
  6. Only if a run exists: inspect job conditions, queue, runner, steps, permissions, external systems.
  7. Repair minimally: change one causal input, repeat the smallest equivalent event, compare evidence.

2. Failure class A — expected a run, but no run exists

Start with a prediction table, not a YAML rewrite:

Question Evidence Common cause
Did the event occur? Commit/PR/API response/audit history Wrong repository, branch, or client call
Was workflow eligible? Workflow path on associated/default branch Manual/scheduled/custom workflow exists only on feature branch
Did branch/tag filter match? Event ref/base branch + glob rules PR filter evaluated against target branch, not head branch
Did path filter match? Changed paths + workflow filter Only docs changed while workflow watches app paths
Was workflow disabled/skipped? Actions settings; commit skip annotation Disabled workflow or explicit skip condition

3. Intentionally broken example — assuming branch OR path

on:
  push:
    branches:
      - main
    paths:
      - 'app/**'

A learner pushes docs/notes.md to main and expects the branch match alone to create a run. It does not. When both filters are defined, both must be satisfied. Preserve the commit SHA and absence of a workflow run. Do not “fix” it by changing runs-on, permissions, or shell because no job was ever queued.

Two valid repairs answer different policy questions:

  • remove the path filter if every main push should create a run;
  • keep the filter if docs-only changes genuinely make the workflow irrelevant.

4. Failure class B — run exists, but the revision is not the one you assumed

The classic example is pull_request. A workflow compares GITHUB_SHA with the contributor's last commit and declares “wrong checkout.” But the event intentionally exposes the PR merge commit as GITHUB_SHA. Diagnose by recording both:

env:
  EVENT_SHA: ${{ github.sha }}
  PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
  REF: ${{ github.ref }}
  BASE_REF: ${{ github.base_ref }}
  HEAD_REF: ${{ github.head_ref }}
steps:
  - run: |
      printf 'event_sha=%s pr_head_sha=%s\n' "$EVENT_SHA" "$PR_HEAD_SHA"
      printf 'ref=%s base=%s head=%s\n' "$REF" "$BASE_REF" "$HEAD_REF"

Repair the expectation first. Only change checkout behavior if the workflow's actual requirement is to test the head commit rather than the merge result.

5. Failure class C — cron did not start at the expected second

Do not interpret schedule delay as a syntax or runner failure until you inspect current schedule rules. Scheduled workflows run from the default branch, have a minimum five-minute interval, and can be delayed during high Actions load. Current docs specifically note that the start of an hour is a high-load period and suggest choosing a different minute when possible.

Production pattern: record both scheduled expression and actual run start timestamp. Alert on a meaningful lateness budget rather than comparing exact wall-clock equality.

6. Failure class D — “fixing” permissions by choosing a more privileged trigger

A fork PR cannot access the same secrets/write capabilities as trusted branch automation, and that is intentional. Switching from pull_request to pull_request_target and then checking out/executing untrusted PR code is not a routine permissions fix. It moves execution into a trusted base context and can create a severe privilege boundary violation.

In Chapter 03, the safe repair is to keep the untrusted validation job read-only. If privileged follow-up is required, separate it behind an explicit trusted control plane and defer the full design to Chapter 22.

7. Failure class E — expecting every GITHUB_TOKEN mutation to trigger another workflow

GitHub deliberately suppresses most workflow-triggering recursion for events caused by the repository's GITHUB_TOKEN. Current behavior makes exceptions for workflow_dispatch and repository_dispatch, and has special approval behavior for certain PR events. If a workflow writes to the repository or an issue and you expected another workflow to cascade automatically, inspect token/event recursion rules before adding retries.

8. Broken trigger mini-lab

Create this disposable workflow on main:

name: Broken trigger lab
on:
  push:
    branches: [main]
    paths: ['app/**']
permissions: {}
jobs:
  prove:
    runs-on: ubuntu-24.04
    steps:
      - run: echo "created for $GITHUB_REF at $GITHUB_SHA"
  1. Commit a docs-only change on main. Predict no run; record commit SHA and absence.
  2. Before editing YAML, explain AND semantics from the current workflow.
  3. Commit an app/** change. Predict a run; record run ID/attempt/ref/SHA.
  4. If your policy wanted docs-only visibility, repair by removing the path filter or moving selective logic to job conditions. Preserve the original evidence.

9. Diagnostic anti-patterns

  • Editing runs-on when no run exists.
  • Adding write-all permissions to “make triggers work.”
  • Dumping the entire event context to logs instead of selecting necessary fields.
  • Replacing pull_request with a privileged trigger to regain secrets.
  • Assuming a schedule must fire exactly at the cron second and repeatedly editing cron without preserving actual timestamps.
  • Deleting failed or surprising runs before recording run IDs, attempts, refs, and payload evidence.
Next lesson

Checkpoint trigger matrix

Predict multiple events before execution, then reconcile created runs and non-runs into a complete evidence packet.

Knowledge check

A docs-only push to main does not create a run for a workflow with branches: [main] and paths: [app/**]. What layer failed?

A PR run uses a SHA different from the head commit. What should you inspect before changing checkout?

Why is a delayed scheduled run not automatically a GitHub-hosted runner problem?

Why is pull_request_target not a normal fix for missing fork secrets?

A workflow uses GITHUB_TOKEN to create an issue and expects another workflow to trigger on that issue. What should you verify?

Official references and version notes

Version and compatibility note

Version-sensitive trigger behavior was rechecked against current primary GitHub documentation on 2026-09-09. Executable labs use a disposable repository, ubuntu-24.04, built-in shell steps only, and explicit permissions: {} because no repository API mutation is required from inside the workflow. At verification time, workflow_dispatch, repository_dispatch, and schedule require the workflow file to exist on the default branch; scheduled workflows run from the latest default-branch commit, support POSIX cron plus an optional IANA timezone, and have a minimum five-minute interval but may be delayed under load. GitHub is continuously delivered, so event payload fields, limits, schedule behavior, and default-branch rules must be rechecked before long-lived production use. The deliberately broken examples remain side-effect free: they create only disposable commits and Actions run history. The correct response to an upstream trigger mismatch is not broader token scope or a more privileged event.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.