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:- GitHub delivers a webhook for a repository the App is installed on.
- auto verifies the delivery signature and resolves the installation to your organization’s GitHub connection.
- 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. - The payload is normalized into a stable shape and stored under an event key like
github.pull_request.opened, then routed to every trigger whoseeventandwherematch.
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.
Shared payload fields
Most GitHub events carry the same metadata alongside their event-specific fields. Triggermessage templates render {{dot.path}} placeholders against this payload, and where filters address it with $.dot.path — always without a payload. prefix.
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:
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— onclosed, distinguish a merge from a plain close$.github.author.bot: false— skip bot-opened PRs
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, with an explicit upsert tool for a single current verdict:
.auto/agents/pr-review.yaml
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.pull_request_read and upsert_issue_comment. Use the upsert tool only for the reviewer’s own verdict. Its outgoing attribution and target agent name come from the session snapshot, not a caller-selected identity. Existing-comment selection additionally requires GitHub’s native author login to match the resolved App bot and its author type to be Bot. Only a single comment with that author and matching agent attribution is eligible, including a verdict from an earlier session of the same agent. A copied body marker alone does not qualify a human or other App’s comment. Incomplete inventories, unknown authorship on a matching marker, or multiple trusted matches fail closed rather than choosing the oldest comment or appending another verdict. This platform-proxy correction is separate from deploying the web service or releasing a managed template. On a new head, re-begin the managed check and review the current head before updating the verdict; never publish or conclude a check with superseded analysis. The canonical reviewer still forbids file edits, pushes, submitted approvals or change requests, and merging.
The September 8, 2026 verdict-tool change is now materialized in merged immutable @auto/agents@1.14.0/pr-review.yaml, whose GitHub allowlist is pull_request_read and upsert_issue_comment. The earlier 1.13.0 entrypoint remains unchanged and still selects add_issue_comment. This is merged registry evidence, not proof that the deployed catalog or a tenant’s @latest installation has advanced. Before relying on the change, verify 1.14.0 or a later version containing that allowlist in the deployed catalog, its resolved installation after normal GitHub Sync, and the tool snapshot of a newly created reviewer session. Existing sessions retain their creation-time snapshots; merging the release does not add a missing tool to them or repair a failed verdict publication. Backend deployment, installation and supported later review cycles remain separate; see managed templates.
To react to the merge itself — before, or independent of, any GitHub Sync apply the merge triggers:
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.
Documentation Maintainer merge identity
The Documentation Maintainer in@auto/agents@1.12.0 treats a merged PR close event’s github.pullRequest.mergeCommitSha as a routing hint, not the final documentation source identity. Before deciding docs impact or deduplicating a source, its workflow is:
- Read fresh PR metadata with
pull_request_read(get), the completeget_filesinventory, andget_diff. - Corroborate the fresh metadata with
list_commitsor the mounted base-branch history to identify the commit that actually integrated the PR. - Use that resolved commit in
<repo>#<pull-request-number>@<merge-commit-sha>. Search the existing documentation branch and open or closed PRs for the source marker before writing.
1.12.0 includes this resolution contract. The immutable @auto/agents@1.11.0 Documentation Maintainer still presents the event SHA directly in its merge prompts and does not contain it. A release merge does not prove that the deployed registry or an existing installation has advanced; verify the resolved version. See managed templates.
Pentester continuation after auto-authored updates
The Pentester GitHub-review overlay in@auto/agents@1.12.0 distinguishes starting a review from continuing one after an auto-authored update. The overlay remains opt-in: its installer must supply githubConnection, and both review paths filter the repository through repoFullName.
Release
1.12.0 includes the auto-authored continuation trigger; the immutable @auto/agents@1.11.0 does not. Confirm that the installation resolves the intended version and enables the GitHub-review overlay. The release does not silently activate that overlay; see managed templates.
The auto-authored continuation uses
route.to.target: github.pull_request with route.onUnmatched: drop. It does not create a review for an unbound auto-authored PR, preventing recursive reviews of the agent’s own report PRs. The non-auto path retains route.onUnmatched: spawn.
Both paths share the security-review check, displayed as Auto security review, including its instructions and deadlines. Unresolved CRITICAL or HIGH findings, a stale head, or Evidence incomplete still fail the check. The begin timeout remains 1,200 seconds and the completion timeout 1,800 seconds, both concluding failure.
An accepted new-head continuation queues the replacement check cycle before dispatching the review message. Redelivery of that same event does not dispatch again or create another check cycle. The new route does not make an older head’s check valid for the repaired head.
Repository Pentester review safeguards
Unlike the reusable overlay above, thefractal-works/auto Pentester facade replaces the inherited prompts and removes all inherited triggers, including PR review and close events. It still pins @auto/agents@1.12.0, but authors a daily merged-PR security sweep instead of per-PR managed checks or verdict comments. That tenant change did not alter published templates or other installations; the separate 1.15.0 release below now supplies the reusable daily contract.
The only trigger is daily-security-sweep: a heartbeat with cron: "0 8 * * *", timezone: America/Los_Angeles and route.to: new. Each scheduled window starts with independent session context; there is deliberately no concurrency: 1 cap. Overlap avoidance is a prompt-level predecessor check, not a transactional lock. If another daily sweep is still active, the instructions defer competing writes and carry the uncovered window in the handoff rather than polling.
The facade sets connections: none to disable implicit project-connection tools. Omitting connections still behaves as all during new-session tool expansion, so removing a named Notion tool alone does not prevent an active project connection from supplying a tool. This opt-out preserves the explicitly authored auto, chat, and github tools without revoking provider grants or changing the platform default. The 1.15.0 entrypoint independently declares the same opt-out.
- Review the exact window. Include all PRs, including auto-authored PRs, merged in
[heartbeat.scheduledAt minus 24 hours, heartbeat.scheduledAt). A manual sweep records its supplied window end or current UTC time. Paginate search results and read completeget,get_filesandget_diffevidence. Verify landed changes against historical PR metadata and a freshly fetched, verifiedmaincheckout; do not assume every merge has two parents. Assess interactions between changes and confirm that a candidate finding still exists on current main. Incomplete evidence is not a clean review. - Carry unresolved work forward. Use
auto.sessions.listwithagent: pentester,includeArchived: trueandlimit: 50, thenauto.sessions.conversationto read the previous daily run’s final Handoff notes. Unresolved items remain in scope beyond 24 hours. If that run failed or lacks a final message, also read the latest earlier usable handoff and record the gap. Missing history does not imply no pending work. The first daily run can use the previous per-PR final message as migration context. Continuity depends on explicit reads, not automatic predecessor-message injection. - Open focused fixes, not verdicts. Confirmed Critical, High and materially exploitable Medium findings permit review-ready, non-draft remediation PRs on deterministic
codex/security-<slug>branches. Search open and closed PRs first; reuse fixes and revalidate rejected approaches instead of reopening them automatically. Refresh main and recheck the flaw and patch before publication. Low-severity hardening alone does not warrant an automatic patch. Analysis and patch construction stay static: regression tests may be authored, but local execution is forbidden and ordinary PR CI supplies that validation. Disclose gaps or hand off an unsafe-to-fix finding rather than inventing a patch. - Report once per window. Slack is required. Start one short
#devthread and finish with one result reply of at most five short bullets covering reviewed PRs, notable findings, patch links and blockers. Clean or no-merge windows need only a sentence, but still reconcile prior handoffs. Retries reuse the thread and existing finding PRs; urgent updates stay in the same thread. Record Slack failures in the final message rather than substituting GitHub comments or claiming delivery. - Leave a usable handoff. End the final assistant message with window bounds, reviewed main SHA and PRs, coverage gaps, pending findings and next actions, remediation branch/PR state, resolved or withdrawn items, and Slack coordinates. Carry pending items until resolved and mark empty categories
none. The ten-minute inactivity archive does not prevent later handoff reads. Do not create finding issues, external ledgers, dated reports or report-only PRs.
create_branch, create_or_update_file, push_files and create_pull_request; upsert_issue_comment is removed. The mount retains main at depth 1, pullRequests: write, checks: read and issues: none. Never edit main, source PR branches or another author’s remediation branch, merge, enable auto-merge, execute exploits, probe live systems, access credentials or change provider security state.
After GitHub Sync, verify one daily trigger with no PR/check triggers, required Slack, connections: none and the explicit auto, chat and github tools without Notion. Existing sessions retain their creation-time snapshots; this source change neither refreshes them nor automatically retires existing checks. A merged facade is not proof of scheduled delivery, handoff retrieval, Slack publication or remediation. Any live test, resource apply or predecessor retirement remains separately authorized.
Canonical daily security sweep
The daily contract is published in@auto/agents@1.15.0/pentester.yaml, replacing weekly campaigns, Slack mention entry points and optional PR-review events with a daily post-merge sweep. Historical 1.12.0–1.14.0 files and the repository’s pinned facade remain unchanged. Before upgrading, confirm the exact deployed version through managed templates; the merged registry alone does not establish the live catalog or an applied installation.
For this 1.15.0 entrypoint, supply repoFullName and an allocated Slack connection named slack. The template’s GitHub App mount defaults to main; Slack reporting defaults to #dev and is required, not optional. connections: none disables implicit connection tools while retaining explicit auto, chat and github tools. Mount capabilities allow contents and pull-request writes, checks and actions reads, and no issues, workflows, secrets or merge access. The GitHub mutation allowlist is only create_branch, create_or_update_file, push_files and create_pull_request.
The sole daily-security-sweep heartbeat uses cron: "0 8 * * *", timezone: America/Los_Angeles and route.to: new. There is no concurrency: 1 cap: each daily window has independent context. The prompt checks for an active predecessor and defers competing writes, recording the uncovered window rather than polling. This is not a transactional overlap lock.
- Verify the window and source. Review all PRs, including auto-authored PRs, merged in
[heartbeat.scheduledAt minus 24 hours, heartbeat.scheduledAt). A manual sweep records its supplied window end or current UTC time. Paginate metadata and diffs, verify actual landed changes and currentmain, and require a reachable attacker path, impact and source evidence. An incomplete review isEvidence incomplete, not a clean result. - Find the installed agent’s handoff. Call
auto.session.getfor the session and installed agent name, thenauto.sessions.listwith that name,includeArchived: trueandlimit: 50. Exclude the current session and read prior final Handoff notes throughauto.sessions.conversation. This supports renamed installs instead of hard-codingpentester. Recover an earlier usable handoff when the last run was interrupted; report gaps and retain unresolved work beyond 24 hours. Continuity requires explicit reads, not scheduler-injected context. - Patch only confirmed notable findings. Critical, High and materially exploitable Medium findings permit focused, non-draft remediation PRs on deterministic
codex/security-<slug>branches. Search open and closed PRs, reuse fixes, revalidate rejected approaches and recheck current main before publication. Low-severity hardening alone does not authorize an automatic patch. Analysis and patch construction remain static: tests may be authored, but ordinary PR CI supplies execution. Never run repository code, dependency installs, hooks, exploits or live probes; access secrets; change provider security settings; edit other authors’ branches; merge; or enable auto-merge. - Report and preserve continuity. Use one
#devthread per window and one result reply of at most five short bullets; a clean or no-merge result can be one sentence, but still reconcile prior handoffs. Retries reuse the thread and patch PRs. Record Slack failure in the final message rather than substituting GitHub comments. End with window bounds, reviewed SHA/PRs, coverage gaps, pending findings/actions, patch state, resolved or withdrawn findings and Slack coordinates, marking empty categoriesnone. The 600-second inactivity archive does not prevent subsequent handoff reads.
pentester/github-review.yaml subpath is an inert compatibility import: explicitly importing it with the former githubConnection variable adds no PR triggers or managed checks. A retained PR binding alone cannot restore those removed triggers. This workflow creates no source-PR verdict comments, finding issues, external ledgers, dated reports or report-only PRs; historical immutable overlays keep their version-specific behavior.
The immutable release is merged; deploying its catalog, adopting it in an authorized installation and obtaining new-session snapshots remain separate steps. Existing sessions keep their prior snapshots; this release does not retire historical checks or prove a live sweep. Verify the applied trigger/tool contract and an authorized run’s predecessor lookup, Slack thread and patch behavior separately. The repository’s daily overrides remain necessary until a separately reviewed adoption replaces 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:
Issues
github.issue.<action>
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:
Comments
GitHub sends oneissue_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:
- 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_requestbind, 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.
Chief readiness-gate notifications
Thefractal-works/auto Chief of Staff facade overrides shepherd-check to listen for github.check_run.completed through github-fractal-works, filtered to that repository. This is a repository-specific override, not a new default for every managed Chief installation.
Its $.github.checkRun.name allowlist contains exactly All checks, Auto PR review, and Auto security review. Other per-job completions do not match this trigger. There is no conclusion filter: matching failures as well as successes prompt a readiness reassessment.
The head filter remains $.github.checkRun.headIsCurrent: { notIn: [false] }. It excludes an explicitly stale result, but unlike an equality filter for true, a missing field does not fail this condition. A notification is not proof that its head is current or that the check succeeded.
Routing remains route.to.target: github.pull_request with route.onUnmatched: drop. The notification continues the Chief session bound to that PR; without a matching binding, this trigger drops the event rather than starting a session.
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.
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 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:
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_requestbind 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.synchronizecaused by an auto session’s push is delivered like any other (withgithub.auto.authored: true); per-head-SHA dedup collapses redeliveries of the same commit to one routed event. - Interrupt vs. defer.
github.check_run.completedandgithub.workflow_run.completeddefer until the session is idle; every other GitHub event interrupts the running turn immediately.
Quick reference
For chat events see Slack events, for issue-tracker events see Linear events, and for auto’s own internal events see Lifecycle events.