Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions .github/workflows/dispatch-review.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Runs the dispatch GATE for AI code reviews on this repo's PRs.
# Forwards to the shared decider workflow in nsheaps/agents, which evaluates
# whether to dispatch a review and (if yes) fires a repository_dispatch to the
# target agent repo's dispatch-receiver-review.yaml.
#
# This file is a template — copy into your repo at
# `.github/workflows/dispatch-review.yaml`. Synced via `nsheaps/.github` CI
# automation when configured; until then, copy-paste.
#
# Spec: https://github.com/nsheaps/agents/blob/main/plugins/claude-code/review-utils/specs/review-dispatch.md
#
# Requirements (provisioned via nsheaps/.github/secret-sync.yaml):
# - AUTOMATION_GITHUB_APP_ID
# - AUTOMATION_GITHUB_APP_PRIVATE_KEY (automation-nsheaps[bot]; installed on
# THIS repo for label edit + check_run
# posting, AND on the target agent repo
# so it can fire repository_dispatch)
#
# Why automation creds (not REVIEW_GITHUB_APP_*)? The gate is routing only —
# it never speaks AS the reviewer. It edits a label, posts a queued check, and
# fires a repository_dispatch. The reviewer-identity (REVIEW_GITHUB_APP_*) is
# owned by the target agent's `dispatch-receiver-review.yaml`, where the review
# actually executes. See plugins/claude-code/review-utils/specs/review-dispatch.md
# §Secrets for the gate-vs-receiver creds rationale.
#
# LLM-auth secrets (REVIEW_ANTHROPIC_API_KEY / CLAUDE_CODE_OAUTH_TOKEN) are
# NOT needed here — owned by the target agent's receiver for the same reason.

name: Dispatch PR Review

on:
pull_request:
types: [opened, reopened, synchronize, ready_for_review, labeled]

# Explicit top-level permissions (mirrors the job-level grant below) so
# checkov's CKV2_GHA_1 ("top-level permissions not write-all") is satisfied.
permissions:
contents: read
pull-requests: write
checks: write

jobs:
review:
# Gate: review fires automatically on any OPEN, non-draft PR event
# (opened, reopened, synchronize, ready_for_review) -- no label needed.
# The `request-review` label only matters to FORCE a review on a DRAFT
# PR (apply the label while it's still a draft). `converted_to_draft`
# does NOT fire a review by itself -- a PR converted to draft is simply
# not reviewed until it's marked ready again or explicitly labeled. If
# you change the request label name, update the literal in the `==`
# comparison below.
Comment on lines +44 to +51

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 P2 — converted_to_draft clarification is confusing because that event isn't in the trigger list

The comment tells the reader converted_to_draft "does NOT fire a review by itself" — but that event isn't in on.pull_request.types above (line 33) at all, so of course it doesn't fire anything. Leading with an event that literally cannot fire this workflow makes the reader hunt for context.

The useful content is: "if a PR is currently a draft, the if: skips it unless request-review is applied." Suggest tightening to that:

Suggested change
# Gate: review fires automatically on any OPEN, non-draft PR event
# (opened, reopened, synchronize, ready_for_review) -- no label needed.
# The `request-review` label only matters to FORCE a review on a DRAFT
# PR (apply the label while it's still a draft). `converted_to_draft`
# does NOT fire a review by itself -- a PR converted to draft is simply
# not reviewed until it's marked ready again or explicitly labeled. If
# you change the request label name, update the literal in the `==`
# comparison below.
# Gate: review fires automatically on any OPEN, non-draft PR event
# (opened, reopened, synchronize, ready_for_review) -- no label needed.
# For a DRAFT PR, the gate skips unless someone applies the
# `request-review` label; that's the only way to force a review on a
# draft. If you change the label name, update the literal in the `==`
# comparison below.

(This also implicitly clarifies what happens post-converted_to_draft — subsequent synchronize events are gated out — without naming a non-triggering event.)

if: |
github.event.pull_request.state == 'open' &&
(
github.event.pull_request.draft != true ||
(github.event.action == 'labeled' && github.event.label.name == 'request-review')
)
# Explicit permissions: default_workflow_permissions is "read" in many
# repos but the called workflow needs pull-requests + checks write.
permissions:
contents: read
pull-requests: write
checks: write
# @main = rolling updates: any change merged to nsheaps/agents takes effect
# on the next PR event in repos using this template. This is intentional —
# operators who need pinned stability should replace @main with a commit SHA
# and update it in lock-step with plugin version bumps.
uses: nsheaps/agents/.github/workflows/review-dispatch.yaml@57debfca1958b3632acd8b9a29bff99573b99993 # main
Comment on lines +64 to +68

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐛 P1 — Comment contradicts the actual uses: ref

The block comment says @main = rolling updates and calls this "intentional", telling operators to replace @main with a SHA if they want pinned stability. But the uses: line right below is already pinned to a SHA:

uses: nsheaps/agents/.github/workflows/review-dispatch.yaml@57debfca1958b3632acd8b9a29bff99573b99993 # main

So a reader is told "this file uses @main for rolling updates" while looking at a SHA-pinned reference. Two failure modes:

  • Ops read the comment, assume rolling behavior, and wait for updates that never arrive (the SHA has to be re-synced).
  • Ops trust the pin, ignore the comment — but then the rationale block is dead weight and misleads the next reader.

Either the sync process rewrote @main<SHA> without updating the comment, or the template intent changed. Please rewrite the rationale to match the actual behavior in the consumer's repo — pinned, refreshed on sync — and drop the "rolling" framing. Example:

Suggested change
# @main = rolling updates: any change merged to nsheaps/agents takes effect
# on the next PR event in repos using this template. This is intentional —
# operators who need pinned stability should replace @main with a commit SHA
# and update it in lock-step with plugin version bumps.
uses: nsheaps/agents/.github/workflows/review-dispatch.yaml@57debfca1958b3632acd8b9a29bff99573b99993 # main
# Pinned to a specific nsheaps/agents SHA; the trailing `# main` records
# which branch the pin was taken from. The `nsheaps/.github` sync automation
# refreshes this SHA when the upstream `main` moves — updates are NOT
# rolling in this consumer repo. Operators wanting immediate rolling
# updates can replace the SHA with `@main` (loses supply-chain pin).
uses: nsheaps/agents/.github/workflows/review-dispatch.yaml@57debfca1958b3632acd8b9a29bff99573b99993 # main

If this file is supposed to use @main directly in consumer repos, then fix the uses: instead — but the SHA pin is the safer default.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 P2 — Trailing # main doesn't identify what was pinned

The # main comment tells you which branch the SHA came from, but not when or what version. Six months from now, this file still says # main while the SHA points at a commit that's hundreds of commits behind — and a reader has no easy way to tell.

Convention in the actions ecosystem (and what Dependabot / Renovate emit) is a stable identifier — a tag, or at minimum a date:

uses: nsheaps/agents/.github/workflows/review-dispatch.yaml@57debfc... # v1.4.2
# or
uses: nsheaps/agents/.github/workflows/review-dispatch.yaml@57debfc... # main @ 2026-08-28

If nsheaps/agents doesn't tag releases, adopting even a lightweight tag on the reusable workflow (or emitting the sync-time date via the sync process) would let operators eyeball staleness without opening the compare view.

# secrets: inherit doesn't pass cross-repo (GitHub limitation).
secrets:
AUTOMATION_GITHUB_APP_ID: ${{ secrets.AUTOMATION_GITHUB_APP_ID }}
AUTOMATION_GITHUB_APP_PRIVATE_KEY: ${{ secrets.AUTOMATION_GITHUB_APP_PRIVATE_KEY }}
# Optional overrides (uncomment to use):
# with:
# target-repo: nsheaps/.ai-agent-henry # default
# event-type: pr-review # default repository_dispatch event_type
Loading