One pass over the repo's open PRs per invocation, then stop. Compose with
/loop for recurrence (/loop /process-prs, /loop 1h /process-prs); /loop
owns pacing and is the new-PR discovery mechanism. Never schedule yourself.
State derives entirely from GitHub (labels, draft flag, commit/comment
timestamps), so a tick is idempotent and resumable: an empty queue costs one
gh pr list and exits.
Rails
- Never merge, never close. The verdict is a recommendation; merging and closing stay the maintainer's hand.
- Never push to a fork. Only branches in the repo itself are editable.
- Every PR comment and every issue starts with a disclaimer. PR comments:
> *This was generated by AI during PR processing.*. Issues:> *This was generated by AI during triage.*. - Skip PRs owned by another active loop. If another dispatcher already drives a PR (its branch is in that loop's queue), leave it untouched. This pass handles everything else.
0. Preflight
-
Abort if not a git repo, or if there is no GitHub remote. This skill is GitHub-only (
gh,authorAssociation, pull requests). GitLab merge requests are a separate concern, not handled here. -
git fetch. Identify the default branch, do not assumemain:gh repo view --json defaultBranchRef -q .defaultBranchRef.name -
Detect optional substrate; use what exists, skip what doesn't:
- Verdict labels (
recommend-merge/recommend-close/recommend-triage, plus the sharedneeds-info). If absent, offer to run/setup-github-labels; without them the tick can review but cannot record a verdict, so stop and say so. - Triage wiring (
docs/agents/triage-labels.md, an issuetype:axis,ready-for-agent/needs-triage) and anissue-tracker.md. Used for spin-off issues. If absent, fall back to plaingh issue createand skip the triage-state labels. A repo with none of this is fine.
- Verdict labels (
Labels
The three PR verdicts are mutually exclusive; a PR carries exactly one once finalized:
recommend-merge: finalized and endorsed, for the maintainer to review and merge.recommend-close: active reject (broken, abandoned, superseded, out of scope, or a technical blocker the agent can name).recommend-triage: code is sound but the merge/close call is a product or scope judgment the agent can't make.
A PR parked on an outside human reuses the shared needs-info label. A PR still
being iterated stays a draft (no verdict label).
Tick algorithm
-
Build the queue. Open, non-draft PRs with no
recommend-*verdict, minus branches owned by another active loop:gh pr list --state open --json number,headRefName,isDraft,author,authorAssociation,labels \ --jq '[.[] | select(.isDraft|not)]'Also pull verdicted PRs to check for staleness (next step).
-
Re-queue stale verdicts. For each PR wearing a
recommend-*label, compare its latest commit time to the agent's last verdict comment. If the commit is newer, the verdict is stale: remove the label and put the PR back in the queue for this tick. -
Classify each queued PR by author:
- Own:
authorAssociationisOWNERorMEMBER. Full finalize. - Bot: author is a bot (Dependabot et al.). Mechanical review.
- Community: everyone else. Review-only.
- Own:
-
Process, respecting throughput. Review all queued community and bot PRs (cheap). Full-finalize at most one own PR per tick, to avoid two rebase chains conflicting. Apply the per-tier pipeline below.
-
Handle in-flight PRs. A PR with no verdict whose checks are still pending and which the agent already pushed/commented on is "awaiting CI", so skip it; the next tick re-checks. This state is derived from GitHub, not stored.
-
Report. End with a short dashboard: each queued PR and the action taken, verdicts emitted, PRs awaiting CI, spin-off issues filed, anything needing the maintainer.
/loopreads this to pace the next run.
Per-tier pipeline
Own PRs: full finalize
Skip the PR this tick if it is already awaiting CI from a prior tick (step 5) — do not re-finalize while checks are pending. Otherwise:
Check out the selected PR before any finalize step. Require a clean
Git-visible working tree; if it is dirty, stop this PR and report the blocker.
Run gh pr checkout <N>, then confirm the current branch matches the PR's
headRefName. Every Git and review step below operates on that checkout.
-
Rebase only if a fresh CI run against current
mainis actually needed. A rebase forces a push, which re-triggers CI and costs a whole extra tick of waiting, so don't pay it reflexively. When CI runs onpull_requestwith a bareactions/checkout(the common case; confirm in the workflow), a green check already tested the PR merged intomain, but only asmainstood when CI last ran, since GitHub does not re-run CI whenmainadvances. So the real question is whether that green has gone stale in a way that matters. Compute the divergence (use the default branch and the PR's head ref, not literalmain):gh pr view <N> --json mergeStateStatus,files \ -q '{state: .mergeStateStatus, files: [.files[].path]}' base=$(git merge-base origin/<defaultBranch> origin/<headRef>) git diff --name-only "$base"..origin/<defaultBranch> # files main changed since base git rev-list --count "$base"..origin/<defaultBranch> # commits main gained since baseRebase locally (step 5 publishes it and re-triggers CI) iff any holds:
- merge state is
DIRTY/conflicting (a textual conflict to resolve), orBEHIND(branch protection requires up-to-date before merge); main's post-base files intersect the PR's changed files, a direct overlap, so the merged result the PR's tests cover may actually differ;maingained more than 20 commits since the merge-base, a catch-all: a large disjoint advance can still carry a transitive/semantic break (a renamed or deleted symbol the PR calls from another file) that the stale green never exercised.
Otherwise skip the rebase entirely.
mainmoved only in files disjoint from the PR and within the cap, so the existing green still reflects the merged result; leave the branch untouched and let the PR be verdicted this same tick (step 6). (If merge state isUNKNOWN, GitHub is still computing mergeability; re-check next tick rather than guessing.) - merge state is
-
Gate
/review-fixbehind a cheap/code-reviewpass. An own PR that is already non-draft has, by convention, been through/review-fixbefore it was un-drafted, so re-running the full review reflexively is usually wasted motion. First run a single report-only/code-reviewat medium effort (no--fix) over the diff as the same cheap safety net the community tier uses, then branch on what it finds:- No meaningful findings (clean, or only nits already matching repo
conventions) → skip
/review-fixentirely; the PR is already settled. Go to step 3. - Meaningful findings (a real correctness or design fix, or several
concerns at once) → run
/review-fix #<N> verify and repair these gate claims: <consolidated gate findings>, substituting the actual report rather than referring to it indirectly. Continue onclean; onincomplete, stop this PR for the tick and report the blocker without publishing or emitting a verdict.
- No meaningful findings (clean, or only nits already matching repo
conventions) → skip
-
If
/review-fixleft edits, rungit-hunk skills get core logical-commitsand usegit-hunkto commit them. Then/recommitto reshape the branch into a clean, logical sequence. -
/verify: only if the PR changes runtime behavior and a smoke is practical./verifypicks the method per project type (CLI invocation, server boot, library import, GUI launch); skip for docs/refactor/test-only PRs. -
When the local branch differs from its remote after the rebase, repair, or recommit steps, run
/sending-pull-requestto push with lease and update the existing PR. Skip it when there is nothing to publish. -
Resolve CI non-blocking. If step 5 had nothing to publish, the existing checks are authoritative: read them directly and verdict now without waiting, applying the outcome rules below. A push made this tick puts the PR into the awaiting-CI state; when its checks are pending, leave the PR awaiting CI and move on, and a later tick applies the same rules. The outcome rules:
- green →
recommend-merge. - red, no prior agent fix-attempt on the PR → one fix-and-repush attempt, then leave it awaiting CI again.
- red on a defect the PR exposed rather than introduced (the failing check
is new coverage this PR adds, and the failure does not trace to a change in
this PR) →
recommend-triage, failure quoted, plus a spin-off issue for the root cause. Landing new coverage that is red on a pre-existing bug is a scope call — take it, narrow the matrix, or fix the bug first — not a verdict the agent can make. - red otherwise, a prior agent fix-attempt already exists →
recommend-close(a red build is a nameable technical blocker, not a sound-code scope call) with the failure quoted.
The one-attempt cap is derived from the PR's history, not stored: a prior agent fix-attempt = a commit the git user pushed to the branch after the first red CI, paired with its disclaimer-prefixed comment. Check the current CI outcome before the cap — a now-green PR takes the green branch regardless.
- green →
-
Emit the verdict with a disclaimer-prefixed comment.
Community PRs: review-only, no pushing
/code-reviewat medium effort, without--fix.- Post one consolidated, disclaimer-prefixed review comment (never inline spam).
- Verdict:
- mergeable as-is, CI green →
recommend-merge. - sound but a product/scope call →
recommend-triage. - broken, abandoned, superseded, out of scope, or red CI with a technical
blocker →
recommend-close, reason quoted. - needs changes the author must make (incl. a trivial CI fix, or checks not
yet authorized) → leave the review comment and park on
needs-info.
- mergeable as-is, CI green →
Bot PRs: mechanical
Review-only; the verdict is mechanical and needs-info never applies:
- patch/minor bump, CI green →
recommend-merge. - red CI →
recommend-close(a failing bump is a nameable technical blocker), failure quoted, unless the bump merely exposed a latent bug rather than broke the build, which is the same scope call as above →recommend-triage. - major version or breaking changelog, CI green →
recommend-triage(a sound bump, but taking it is a scope call).
Spin-off issues
While processing a PR, this pass captures two kinds of work as GitHub issues, autonomously but guarded:
- Follow-up: a loose end from this PR that should not block its merge (a
TODO left behind, a test-coverage gap, a deferred refactor). The PR still gets
its verdict; note the filed issues in the verdict comment
(
follow-ups: #N, #M). A follow-up never blocks the parent's merge. - Idea: a standalone improvement noticed while reading the code.
For every spin-off issue:
- Dedup-search open issues first; skip if one already covers it.
- Create it with
gh issue create(or the repo'sissue-tracker.mdconventions), including the triage disclaimer and a link back to the originating PR. When the triage substrate exists, add onetype:label and theneeds-triagestate so the maintainer can route it. Without that substrate, create a plain issue. - List it in the tick report.
Composition
The spin-off issues filed here enter needs-triage when the repository has the
triage substrate. A maintainer routes each one to ready-for-agent or
ready-for-human; /implement-issues handles agent-ready work, whose resulting
PR flows back into this pass. None of these passes ever merges.