> ## Documentation Index
> Fetch the complete documentation index at: https://docs.auto.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# GitHub Events

> Every GitHub event a trigger can bind — payload fields, template placeholders, where-filter paths, and routing guidance for each event key.

This page catalogs every GitHub event key an agent trigger can listen on, the normalized payload each one carries, and the routing shape that fits it. Read [Triggers](/reference/triggers) first for the general trigger schema; this page assumes you know what `event`, `where`, `message`, and `routing` do.

## How GitHub events reach your triggers

GitHub webhooks arrive through auto's GitHub App and are normalized into provider-neutral event records before routing:

1. GitHub delivers a webhook for a repository the App is installed on.
2. auto verifies the delivery signature and resolves the installation to your organization's GitHub connection.
3. The connection resource must subscribe to the raw webhook event name (`pull_request`, `issue_comment`, …). Unsubscribed events are dropped at ingress and never reach a trigger.
4. The payload is normalized into a stable shape and stored under an event key like `github.pull_request.opened`, then routed to every trigger whose `event` and `where` match.

A GitHub trigger names its connection so apply can resolve the grant:

```yaml theme={null}
triggers:
  - event: github.pull_request.opened
    connection: github-acme
    where:
      $.github.repository.fullName: acme/widgets
    routing:
      kind: spawn
```

The raw webhook names a connection may subscribe to are `push`, `pull_request`, `check_run`, `issues`, `issue_comment`, `pull_request_review`, `pull_request_review_comment`, `pull_request_review_thread`, `workflow_run`, and `commit_comment`. A connection's default subscription is `["pull_request"]` — if a trigger on another family never fires, check the connection's `events` list first.

<Warning>
  Trigger event keys are not validated against a closed catalog. A typo like `github.pull_request.close` (instead of `closed`) applies cleanly and silently never fires. Copy keys from the tables on this page.
</Warning>

## Shared payload fields

Most GitHub events carry the same metadata alongside their event-specific fields. Trigger `message` templates render `{{dot.path}}` placeholders against this payload, and `where` filters address it with `$.dot.path` — always without a `payload.` prefix.

| Field                     | Description                                                                                                                                                                                                            |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `github.repository`       | `{ id, fullName, htmlUrl, owner, name }` — filter on `$.github.repository.fullName` to scope a trigger to one repo                                                                                                     |
| `github.author`           | `{ id, login, type, bot }` — the author of the content that fired the event (comment or review author when there is one, else the webhook sender). The stable author filter path across the event kinds that carry one |
| `github.auto.authored`    | `true` when auto's own GitHub App authored the content. Filter `$.github.auto.authored: false` so agents never react to their own comments                                                                             |
| `github.auto.externalBot` | `true` when the author is a bot other than auto's App. Filter `$.github.auto.externalBot: false` to drop third-party bot noise (CI bots, status bots) while keeping human and auto-authored events                     |
| `github.auto.mentioned`   | `true` when the body mentions auto                                                                                                                                                                                     |
| `github.auto.attribution` | `{ version, sessionId, agentName }` when the content was produced by an auto session                                                                                                                                   |
| `github.auto.checkRerun`  | Present when the body carries the `/auto rerun [check-name]` platform command                                                                                                                                          |
| `github.action`           | The webhook action (`opened`, `created`, `labeled`, …) — also the last segment of the event key                                                                                                                        |
| `github.installationId`   | The GitHub App installation id                                                                                                                                                                                         |
| `github.sender`           | The raw webhook sender (`{ id, login, type }`)                                                                                                                                                                         |
| `raw`                     | The unmodified GitHub webhook payload, for fields the normalization does not lift                                                                                                                                      |

Not every event carries the full set: `push`, `workflow_run.completed`, and `check_run` events have no `github.auto` metadata, and of those only `check_run.rerequested` carries `github.author`.

Events about a specific pull request or issue also carry a top-level `artifact` identifying it. The artifact is what `bind` routing resolves: a session bound to the `github.pull_request` target receives every later event carrying that PR's artifact.

## Pull requests

### `github.pull_request.<action>`

Fires on pull request lifecycle changes. Ingested actions:

| Event key                         | Fires when                              |
| --------------------------------- | --------------------------------------- |
| `github.pull_request.opened`      | A PR is opened                          |
| `github.pull_request.edited`      | The title, body, or base branch changes |
| `github.pull_request.synchronize` | New commits are pushed to the PR head   |
| `github.pull_request.reopened`    | A closed PR is reopened                 |
| `github.pull_request.closed`      | The PR is closed — merged or not        |

Payload: the shared fields plus `github.pullRequest` with `id`, `nodeId`, `number`, `title`, `htmlUrl`, `headSha`, `headRef`, `baseSha`, `baseRef`, `state`, `merged`, `closedAt`, `mergeable`, `mergeableState`, `mergeCommitSha`, and `body`.

Useful placeholders: `{{github.pullRequest.number}}`, `{{github.pullRequest.title}}`, `{{github.pullRequest.headSha}}`, `{{github.repository.fullName}}`, `{{github.action}}`.

Useful filters:

* `$.github.repository.fullName: acme/widgets` — scope to one repository
* `$.github.pullRequest.merged: true` — on `closed`, distinguish a merge from a plain close
* `$.github.author.bot: false` — skip bot-opened PRs

Routing: these events carry the `github.pull_request` bind target, and the idiomatic shape folds a PR's whole lifecycle into one session — `routing: { kind: bind, target: github.pull_request, onUnmatched: spawn }`. The first event for a PR finds no bound session and spawns one; the spawn claims the PR binding, and every later `synchronize`, comment, and review routes back into the same session instead of spawning parallel workers. `checks:` declarations (managed GitHub check runs the trigger drives) are legal only on `github.pull_request.*` events.

A complete reviewer agent using this shape, adapted from the packaged `@auto/agents@latest/pr-review.yaml` role:

```yaml .auto/agents/pr-review.yaml theme={null}
name: pr-review
systemPrompt: |
  You are the code reviewer for acme/widgets. You are the one reviewer
  session for your pull request: updates route back to you instead of
  spawning another reviewer. When a new head arrives mid-review, discard
  analysis of the older head and re-review the current head.
initialPrompt: |
  Review pull request #{{github.pullRequest.number}} in
  {{github.repository.fullName}}. Call checks.begin with
  { "name": "pr-review" } before doing anything else, then conclude the
  check with checks.success or checks.failure.
mounts:
  - kind: git
    repository: acme/widgets
    mountPath: /workspace/repo
    ref: refs/pull/{{payload.github.pullRequest.number}}/head
    depth: 1
    auth:
      kind: githubApp
      capabilities:
        contents: read
        pullRequests: write
        issues: write
        checks: read
workingDirectory: /workspace/repo
tools:
  auto:
    kind: local
    implementation: auto
  github:
    kind: github
    tools:
      - pull_request_read
      - add_issue_comment
triggers:
  - name: pr-events
    events:
      - github.pull_request.opened
      - github.pull_request.reopened
      - github.pull_request.synchronize
    connection: github-acme
    where:
      $.github.repository.fullName: acme/widgets
    message: |
      Pull request #{{github.pullRequest.number}} has a review-triggering
      update (action: {{github.action}}; head {{github.pullRequest.headSha}}).
      Call checks.begin, then re-review the current head.
    checks:
      - name: pr-review
        displayName: Auto PR review
        description: Reviews this pull request and reports whether blocking issues were found.
        beginTimeout:
          seconds: 1200
          conclusion: failure
    routing:
      kind: bind
      target: github.pull_request
      onUnmatched: spawn
  - name: pr-conversation
    events:
      - github.issue_comment.created
      - github.pull_request_review.submitted
      - github.pull_request_review_comment.created
    connection: github-acme
    where:
      $.github.repository.fullName: acme/widgets
      $.github.auto.authored: false
      $.github.auto.externalBot: false
    message: |
      A PR conversation update arrived for PR #{{github.pullRequest.number}}.
      Read it and decide whether the review needs a refresh.
    routing:
      kind: bind
      target: github.pull_request
      onUnmatched: drop
```

<Note>
  Mount `ref` templates are the one place the `payload.` prefix is correct (`refs/pull/{{payload.github.pullRequest.number}}/head`). Trigger `message`, `initialPrompt`, and `displayTitle` templates render against the bare payload and reject `{{payload.…}}` tokens at apply time.
</Note>

To react to the merge itself — before, or independent of, any [GitHub Sync](/concepts/github-sync) apply the merge triggers:

```yaml theme={null}
triggers:
  - event: github.pull_request.closed
    connection: github-acme
    where:
      $.github.pullRequest.merged: true
    message: |
      PR #{{github.pullRequest.number}} merged
      ({{github.pullRequest.mergeCommitSha}}).
    routing:
      kind: bind
      target: github.pull_request
      onUnmatched: drop
```

`closed` events are lifecycle facts, not conversation: they never start GitHub Sync plans or mergeability checks at ingress, and the closed-PR conversation suppression described below does not apply to them.

### `github.pull_request.merge_conflict` (synthetic)

auto runs a mergeability check whenever a PR head moves (`opened`, `reopened`, `synchronize`) or its base branch receives a push — one check per open PR whose base is the pushed branch. When the check resolves to a real conflict, auto emits this synthetic event — GitHub itself has no conflict webhook. The payload is the triggering PR event's payload with `type` rewritten and `github.pullRequest.mergeable`, `mergeableState`, and `mergeCommitSha` refreshed to the conflict state. A conflict detected from a base-branch push instead carries `github.action: "base_updated"` and `github.basePush { ref, branch, before, after }` describing the push that moved the base. Deduplicated per head + base SHA pair, so one conflict fires once.

Routing: carries the `github.pull_request` bind target — deliver it to the session that owns the PR:

```yaml theme={null}
triggers:
  - event: github.pull_request.merge_conflict
    connection: github-acme
    where:
      $.github.repository.fullName: acme/widgets
    message: |
      PR #{{github.pullRequest.number}} now conflicts with its base branch.
      Rebase onto {{github.pullRequest.baseRef}} and resolve the conflicts.
    routing:
      kind: bind
      target: github.pull_request
      onUnmatched: drop
```

## Issues

### `github.issue.<action>`

| Event key                                         | Fires when                  |
| ------------------------------------------------- | --------------------------- |
| `github.issue.opened`                             | An issue is opened          |
| `github.issue.edited`                             | Title or body edited        |
| `github.issue.closed` / `github.issue.reopened`   | State changes               |
| `github.issue.labeled` / `github.issue.unlabeled` | A label is added or removed |

Payload: shared fields plus `github.issue` (`id`, `nodeId`, `number`, `title`, `htmlUrl`, `body`, `state`, `labels` as a string array) and, on label events, `github.label { name }`.

Useful filters: `$.github.action: labeled`, `$.github.label.name: agent-fix`, `$.github.issue.labels: { contains: "bug" }`.

Routing: carries the `github.issue` bind target. Label-driven kickoff is the common pattern — spawn a session when a human labels an issue for an agent, and bind it so follow-up comments route back:

```yaml theme={null}
triggers:
  - event: github.issue.labeled
    connection: github-acme
    where:
      $.github.repository.fullName: acme/widgets
      $.github.label.name: agent-fix
    message: |
      Issue #{{github.issue.number}} was labeled for you:
      {{github.issue.title}}
      {{github.issue.htmlUrl}}

      Investigate and open a fix PR.
    routing:
      kind: spawn
      bind:
        target: github.issue
```

## Comments

GitHub sends one `issue_comment` webhook for comments on both issues and pull requests. auto splits it into two event families so PR conversation and issue conversation bind to the right target.

### `github.issue_comment.<action>` — comments on pull requests

Actions: `created`, `edited`, `deleted`. Fires when the comment's parent issue **is a pull request**. Payload: PR artifact, `github.pullRequest` reference, and `github.issueComment` with `id`, `nodeId`, `htmlUrl`, `body`, `authorAssociation` (GitHub's `OWNER` / `MEMBER` / `COLLABORATOR` / `CONTRIBUTOR` / `NONE` permission signal), `createdAt`, `updatedAt`, `author`, and any `attachments`.

Useful filters: `$.github.auto.authored: false` (never react to your own comments), `$.github.auto.externalBot: false`, `$.github.issueComment.authorAssociation: MEMBER`.

Routing: `bind` on `github.pull_request` — see the `pr-conversation` trigger in the reviewer example above.

### `github.issue.comment.<action>` — comments on plain issues

Actions: `created`, `edited`, `deleted`. The non-PR half of the split: payload carries an issue artifact, `github.issue` reference (`number`, `title`, `htmlUrl`), and the same `github.issueComment` shape (without `authorAssociation`). Routing: `bind` on `github.issue`.

## Reviews

### `github.pull_request_review.<action>`

Actions: `submitted`, `edited`, `dismissed`. Payload: PR artifact, `github.pullRequest`, and `github.review` with `id`, `nodeId`, `htmlUrl`, `body`, `state`, `commitId`, `submittedAt`, `author`, and `attachments`.

Filter on the verdict: `$.github.review.state: changes_requested` or `approved`. Routing: `bind` on `github.pull_request`.

### `github.pull_request_review_comment.<action>`

Actions: `created`, `edited`, `deleted`. Inline diff comments. Payload: PR artifact, `github.pullRequest`, and `github.reviewComment` with `id`, `reviewId`, `htmlUrl`, `body`, `path`, `diffHunk`, `commitId`, `originalCommitId`, `line`, `originalLine`, `startLine`, `side`, `startSide`, `author`, and `attachments` — enough to locate the exact code the reviewer is pointing at. Routing: `bind` on `github.pull_request`.

### `github.pull_request_review_thread.<action>`

Actions: `resolved`, `unresolved`. Payload: PR artifact, `github.pullRequest`, and `github.reviewThread { id, nodeId, path, line, side }`. Routing: `bind` on `github.pull_request` — for example, waking the PR session when a human resolves the last open thread.

## Checks and CI

### `github.check_run.completed`

Fires when a check run finishes. Only `action=completed` is ingested, and the check run **must be associated with a pull request** — check runs without PR association are dropped at ingress. For CI results on non-PR branches (pushes to `main`), use `github.workflow_run.completed` instead.

Payload: PR artifact, `github.pullRequest` (`number`, `headSha`, …), and `github.checkRun` with `id`, `nodeId`, `name`, `headSha`, `status: "completed"`, `conclusion`, `htmlUrl`, `externalId`, `headIsCurrent`, and `app { slug, owner }`. `headIsCurrent` is `true` when the run's head is still the PR's head at delivery time — a `false` value means a newer push already superseded the run.

Useful filters: `$.github.checkRun.conclusion: failure`, `$.github.checkRun.name: build`, `$.github.checkRun.headIsCurrent: true`.

Routing: `bind` on `github.pull_request`. The canonical CI-fix loop:

```yaml theme={null}
triggers:
  - event: github.check_run.completed
    connection: github-acme
    where:
      $.github.repository.fullName: acme/widgets
      $.github.checkRun.conclusion: failure
    message: |
      Check "{{github.checkRun.name}}" failed on PR
      #{{github.pullRequest.number}} (head {{github.checkRun.headSha}}).
      {{github.checkRun.htmlUrl}}

      Inspect the failure and push a fix.
    routing:
      kind: bind
      target: github.pull_request
      onUnmatched: drop
```

Two delivery behaviors are specific to this event:

* **Defers until idle.** A delivered check completion never interrupts the session's running turn; it waits for the session to go idle. A session mid-push is not derailed by the CI result of its previous head.
* **Stale-head suppression.** When the event routes through a `github.pull_request` bind, a check completion for a head auto has seen before that is no longer the PR's current head is dropped — superseded runs never trigger redundant fix cycles. Completions for the current head, or for a head auto has never seen, always deliver.

### `github.check_run.rerequested`

Fires when someone presses **Re-run** on a check in the GitHub UI. Payload: PR artifact, `github.checkRun { id, name, headSha, htmlUrl }`, `github.pullRequest`, and `github.author` (who pressed the button). Routing: `bind` on `github.pull_request` — deliver "the human asked for a re-run" to the session that owns the check.

### `github.check_run.requested_action.<identifier>`

Fires when someone presses a custom action button on a check run. The button's identifier is baked into the event key, so a check offering a `fix` button produces `github.check_run.requested_action.fix`. Payload: PR artifact, `github.requestedAction { identifier }`, `github.checkRun`, and `github.pullRequest`. Unlike check completions, requested actions interrupt the running turn immediately — a human pressed a button and expects a response.

### `github.workflow_run.completed`

Fires when a GitHub Actions workflow run ends. Only `action=completed` is ingested. This is the general "CI finished" primitive for the cases `check_run.completed` cannot cover — it carries **no PR artifact** and works for pushes to any branch.

Payload: `github.workflowRun` with `id`, `nodeId`, `name`, `path`, `headBranch`, `headSha`, `status: "completed"`, `conclusion`, `htmlUrl`, `runNumber`, `runAttempt`, `createdAt`, `updatedAt`, `runStartedAt`, plus `github.workflow` (`id`, `name`, `path`, `state`) and the repository.

Useful filters: `$.github.workflowRun.path: .github/workflows/deploy.yml`, `$.github.workflowRun.headBranch: main`, `$.github.workflowRun.conclusion: success`.

Routing: no bind target — use `deliver` (typically `routeBy: { kind: attributedSessions }` to reach the sessions whose work produced the run) or `spawn`. Deliveries defer until the session is idle, like check completions.

```yaml theme={null}
triggers:
  - event: github.workflow_run.completed
    connection: github-acme
    where:
      $.github.repository.fullName: acme/widgets
      $.github.workflowRun.path: .github/workflows/deploy.yml
      $.github.workflowRun.headBranch: main
      $.github.workflowRun.conclusion: success
    message: |
      The deploy workflow passed on main.
      Run: {{github.workflowRun.htmlUrl}}
    routing:
      kind: deliver
      routeBy:
        kind: attributedSessions
      onUnmatched: drop
```

## Pushes

### `github.push`

Fires on any push, including branch creation and deletion. Payload: `github` with `installationId`, `repository`, `ref` (`refs/heads/main`), `branch`, `before`, `after`, `created`, `deleted`, and `sender`.

Useful filters: `$.github.branch: main`, `$.github.created: true`, `$.github.deleted: false`, `$.github.repository.fullName`.

Routing: no bind target — a `bind` route can never resolve a push; use `spawn` or `deliver`. Pushes to a Sync-enabled production branch also start a [GitHub Sync](/concepts/github-sync) apply, and any push starts a mergeability check for each open PR targeting the pushed branch (surfacing as `github.pull_request.merge_conflict` when a conflict materializes) — platform side effects independent of your triggers.

## Commit comments

### `github.commit_comment.created`

Fires when a comment is attached to a commit. Only `action=created` is ingested. Payload: `github.comment` with `id`, `nodeId`, `commitSha`, `body`, `htmlUrl`, `createdAt`, `updatedAt`, and `author`, plus the repository and shared metadata. No bind target — route with `deliver` or `spawn`. Deliveries interrupt immediately.

Deploy bots that comment a preview URL onto the deployed commit make this the natural "deploy landed" signal:

```yaml theme={null}
triggers:
  - event: github.commit_comment.created
    connection: github-acme
    where:
      $.github.repository.fullName: acme/widgets
    message: |
      Deploy comment on {{github.comment.commitSha}}:
      {{github.comment.body}}
      {{github.comment.htmlUrl}}
    routing:
      kind: deliver
      routeBy:
        kind: attributedSessions
      onUnmatched: drop
```

## Delivery behavior

Rules the router applies to GitHub events after a trigger matches:

* **Echo suppression is yours to declare.** The platform tags auto-authored content (`$.github.auto.authored`) and third-party bots (`$.github.auto.externalBot`) but does not filter them for you — add both filters to conversation triggers, as in the reviewer example.
* **Closed-PR conversation is suppressed.** Comment, review, review-comment, and review-thread events routed through a `github.pull_request` bind are dropped when the PR is already closed or merged. Archived work does not reopen sessions.
* **Duplicate comment deliveries are deduplicated by body**, so a redelivered webhook cannot make an agent process the same comment twice.
* **auto's own head pushes do route.** A `pull_request.synchronize` caused by an auto session's push is delivered like any other (with `github.auto.authored: true`); per-head-SHA dedup collapses redeliveries of the same commit to one routed event.
* **Interrupt vs. defer.** `github.check_run.completed` and `github.workflow_run.completed` defer until the session is idle; every other GitHub event interrupts the running turn immediately.

## Quick reference

| Event key                                                         | Bind target           | Notes                                               |
| ----------------------------------------------------------------- | --------------------- | --------------------------------------------------- |
| `github.pull_request.{opened,edited,synchronize,reopened,closed}` | `github.pull_request` | `checks:` legal only here                           |
| `github.pull_request.merge_conflict`                              | `github.pull_request` | synthetic, from mergeability checks                 |
| `github.issue.{opened,edited,closed,reopened,labeled,unlabeled}`  | `github.issue`        | `github.label.name` on label events                 |
| `github.issue_comment.{created,edited,deleted}`                   | `github.pull_request` | comments on PRs                                     |
| `github.issue.comment.{created,edited,deleted}`                   | `github.issue`        | comments on plain issues                            |
| `github.pull_request_review.{submitted,edited,dismissed}`         | `github.pull_request` | `$.github.review.state`                             |
| `github.pull_request_review_comment.{created,edited,deleted}`     | `github.pull_request` | inline diff comments                                |
| `github.pull_request_review_thread.{resolved,unresolved}`         | `github.pull_request` |                                                     |
| `github.check_run.completed`                                      | `github.pull_request` | PR required; defers until idle; stale heads dropped |
| `github.check_run.rerequested`                                    | `github.pull_request` | the Re-run button                                   |
| `github.check_run.requested_action.<identifier>`                  | `github.pull_request` | identifier in the key; interrupts                   |
| `github.workflow_run.completed`                                   | —                     | works without a PR; defers until idle               |
| `github.commit_comment.created`                                   | —                     |                                                     |
| `github.push`                                                     | —                     | branch create/delete included                       |

For chat events see [Slack events](/reference/events/slack), for issue-tracker events see [Linear events](/reference/events/linear), and for auto's own internal events see [Lifecycle events](/reference/events/lifecycle).
