chapter: 04·part: The Individual (one workflow)

Triggers: When Workflows Wake Up

Choose the right on: events so the Repo Assistant runs at exactly the right moments and no others.

Objective

By the end of this chapter you can choose the right on: events so the Repo Assistant runs at exactly the right moments — and no others. You'll know the everyday triggers (issues, pull requests, comments, schedules, manual runs), the safe defaults gh-aw applies, and the cost-and-security guardrails that come attached to when a workflow fires.

Everything targets gh aw v0.81.6. We keep evolving the same Repo Assistant from Chapter 3 — this time teaching it to wake up both on a new issue and on a nightly sweep.

Concept: event-driven work (the outer loop's clock)

In Chapter 1 we framed gh-aw as automation for the repository's outer loop — the judgment work that happens around the edges of writing code. But an outer-loop teammate is only useful if it shows up at the right time. A triager who reads issues a week late is worse than none. The trigger is the workflow's clock: it decides the precise moment the agent is worth spending money and attention on.

Two shapes of “the right moment”

Almost every useful trigger is one of two shapes:

  • Reactive — something happened, respond now. An issue was opened, a PR was pushed, someone left a comment. The event carries a payload (the issue, the PR) that is the work.
  • Proactive — on a rhythm, go look for work. A nightly sweep for stale issues, a weekly docs audit. Nothing “happened”; the schedule itself is the prompt.

Great agentic teammates use both. A human maintainer answers issues as they arrive and does a Friday-afternoon cleanup; the Repo Assistant should too. The rest of this chapter is about expressing those two shapes precisely — and about the fact that choosing a trigger is also a security and cost decision, because it decides who and what can make your agent run.

In gh-aw: the on: block and its events

Triggers live in the on: block of the frontmatter. gh-aw “supports all standard GitHub Actions triggers plus additional enhancements for reactions, cost control, and advanced filtering” (Triggers). The simplest form is pure Actions syntax:

The minimal reactive trigger — run when an issue is opened
on:
  issues:
    types: [opened]

The everyday events

You'll reach for a small set of triggers constantly. Each one hands the agent a different payload to reason about:

TriggerFires when…Typical use
issues:an issue is opened, edited, labeled, closed…triage, auto-response
pull_request:a PR is opened, synchronized, labeled…review, CI-doctor
issue_comment:someone comments on an issue or PRChatOps, follow-ups
schedule:a recurring time arrivessweeps, audits, reports
workflow_dispatch:you run it manually (UI, API, or gh aw run)testing, on-demand tasks
workflow_run:another workflow (e.g. CI) completesreact to build failures

When a pull_request or comment event fires, “the coding agent has access to both the PR branch and the default branch” (Triggers) — the context it needs to actually review the change.

Human-friendly schedules

For proactive work, gh-aw improves on raw cron. You can write “human-friendly expressions” that compile to cron, and even use fuzzy scheduling, which “scatter[s] execution times to avoid load spikes” (Schedule Syntax):

Three ways to say “roughly every day”
on:
  schedule: daily                          # compiler picks a scattered time
  # schedule: daily around 14:00           # ±1 hour around 2pm UTC
  # schedule: daily between 9:00 and 17:00 # scattered within business hours
  # schedule:
  #   - cron: "30 6 * * 1"                 # or exact cron: Monday 06:30 UTC

The compiler “assigns each workflow a unique, deterministic execution time based on the file path, ensuring load distribution and consistency across recompiles” (Schedule Syntax). If a hundred repos all say daily, they won't all stampede at midnight.

Shorthands: the one-line trigger

Many triggers have a natural-language shorthand string that “expands into standard GitHub Actions trigger syntax and automatically includes workflow_dispatch” so you can always run the workflow by hand (Triggers):

Shorthands that read like English
on: issue opened                     # issues: [opened]
on: issue labeled bug                # issues labeled "bug" only
on: pull_request opened affecting docs/**   # PR touching docs paths
on: push to main                     # push to a branch
on: daily                            # a fuzzy daily schedule

Feedback and cost controls attached to the trigger

Two enhancements ride along in the same on: block and matter for every workflow you ship:

  • reaction: adds an emoji to the triggering item so a human sees the agent noticed — "eyes" when it starts, for instance. “The reaction is added to the triggering item” (Triggers).
  • stop-after: “automatically disable[s] workflow triggering after a deadline to control costs” — e.g. stop-after: "+30d". “Recompiling the workflow resets the stop time” (Triggers). It's a seatbelt for scheduled jobs that would otherwise run forever.

When to use which trigger (and safe defaults)

Choosing a trigger is mostly about matching the two shapes from the concept — but a few defaults exist specifically to stop a trigger from becoming an attack vector. These are the parts a reviewer should always check.

Safe defaults you get for free

  • Forks are blocked by default. “Pull request workflows block forks by default for security” — you opt specific forks in with the forks: field (Triggers). This is the front line against a malicious PR trying to run your agent with your secrets.
  • Who can trigger is an allowlist. Unsafe triggers (push, issues, pull_request) “automatically enforce permission checks.” The roles: filter defaults to [admin, maintainer, write], and “failed checks cancel the workflow with a warning” (Triggers). A drive-by issue from a stranger won't spend your credits unless you widen the allowlist.
  • workflow_run is hardened. The compiler “injects repository ID and fork checks to reject cross-repository or fork-triggered runs,” and warns (or errors in strict mode) if you don't scope branches: (Triggers).

A quick decision guide

You want to…Reach for
respond to each new issue/PRissues: / pull_request: with types:
do periodic maintenanceschedule: (prefer fuzzy daily/weekly)
let humans invoke on demandworkflow_dispatch:
answer a /command in a commentslash_command:
react to CI resultsworkflow_run: with branches:
trigger from an external system (Jira, PagerDuty)repository_dispatch:

When not to

  • Don't trigger on high-frequency events without a filter. on: push to a busy repo, or issue_comment on every comment, can fire constantly — each run costs AI credits. Filter by label (names:), path (affecting), or a slash_command so the agent runs only when it's genuinely wanted.
  • Don't open the fork gate casually. forks: ["*"] lets any fork trigger your workflow; use the narrowest pattern that meets the need, and pair it with the security model in Chapter 7.
  • Don't leave a scheduled workflow uncapped. A nightly job with no stop-after: and no budget will run indefinitely. Cost controls belong on the trigger, and we return to budgets in Chapter 13.

Worked example: Repo Assistant on issues plus a nightly schedule

Let's give the Repo Assistant both shapes at once: it triages each new issue reactively and runs a nightly stale-issue sweep proactively. The whole thing is one file, and it compiles cleanly under strict mode with no engine key.

examples/ch04/repo-assistant-triggers.md — two triggers, one assistant (compiles: 0 errors, 0 warnings)
on:
  issues:
    types: [opened, reopened]
  schedule: daily
  workflow_dispatch:
  reaction: eyes
  stop-after: "+30d"
permissions:
  contents: read
  issues: read
engine: copilot
network: defaults
safe-outputs:
  add-comment:
    max: 1
  add-labels:
    allowed: [bug, enhancement, question, documentation, needs-info, stale]
    max: 3

Read the on: block as the assistant's clock. It wakes up three ways — a new/reopened issue, a fuzzy daily schedule, or a manual workflow_dispatch — drops an :eyes: reaction on whatever triggered it, and stops firing 30 days after compilation unless you recompile. Everything else is the safe posture from earlier chapters: read-only permissions:, the default Copilot engine, curated network:, and writes routed through safe-outputs: (the subject of Chapter 6).

Because the same file now handles two kinds of run, the Markdown body branches on github.event_name:

The body decides its job from which trigger fired
# Repo Assistant — triage on open, sweep on a schedule

Check `${{ github.event_name }}` first.

## If an issue was just opened or reopened (`issues`)
Read the triggering issue, post one triage comment, apply the best label.

## If this is the daily schedule (`schedule`) or a manual run
No single issue triggered this run — do a **daily sweep**: find open issues with
no activity in 30 days and, for the clearly abandoned ones, add `stale` and a
gentle comment. Be conservative: when in doubt, leave the issue alone.

Compile it exactly as before — offline, no secrets:

Verifying the example
gh aw compile examples/ch04/repo-assistant-triggers.md
# ✓ examples\ch04\repo-assistant-triggers.md (102.8 KB)
# ✓ Compiled 1 workflow(s): 0 error(s), 0 warning(s)

Recap & what's next

You can now make a workflow wake up at exactly the right moments:

  • Triggers are the outer loop's clock, and they come in two shapes: reactive (issues, PRs, comments) and proactive (schedules).
  • The on: block is standard Actions syntax plus gh-aw enhancements: human-friendly and fuzzy schedules, one-line shorthands, reaction: feedback, and stop-after: cost control.
  • Choosing a trigger is a security and cost decision. Forks are blocked by default, roles: gates who can trigger, and workflow_run is hardened against cross-repo abuse — safe by default, widened deliberately.
  • Filter high-frequency events and cap scheduled ones, so the agent runs only when it's worth it.

What's next. The assistant now wakes at the right time — but which brain does it think with? In Chapter 5: Engines, we choose and configure the engine (Copilot, Claude, Codex, or Gemini) and see the portability that engine-neutral design buys you.