claude-code-review.yml
Run a read-only Claude review of a pull request (the code-review plugin). By default, callers run it only on dispatched runs (for @claude review via claude.yml), which posts a consolidated summary. If the caller also enables a pull_request trigger, it can run automatically on PR updates. A direct /review path is available too, but only if the caller also enables issue_comment. claude.yml dispatches it via workflow_dispatch; keep the stub named claude-code-review.yml (or set claude.yml’s review-workflow-file to match) so the dispatch resolves.
If you have disabled claude.yml, enable issue_comment in the stub and set the repository variable CLAUDE_AGENT_DISABLED to true, so its dispatch-on-comment job also accepts an @claude review mention. Both are required: that job only ever runs on an issue_comment event, so the variable on its own changes nothing. Without them a repo with the agent off has no mention path, and the phrasing people reach for silently does nothing. Leave the variable unset while the agent is live, or the mention is answered twice.
Inputs
| Input | Type | Default | Description |
|---|---|---|---|
pr-number |
string | '' |
PR number to review (set by claude.yml’s workflow_dispatch; empty on pull_request runs). |
prompt-addendum |
string | '' |
Repo-specific review guidance appended to the plugin prompt. |
checkout-submodules |
boolean | false |
Check out submodules so the reviewer can read their contents. Private submodules require the SUBMODULES_TOKEN secret. |
allowed-bots |
string | 'github-actions[bot]' |
Comma-separated bot actors the reviewer accepts, so a dispatched review is not rejected as non-human. |
track-progress |
boolean | false |
Ignored (gha#580). Tag mode posts a live tracking comment through the github_comment MCP server (mcp__github_comment__update_claude_comment) and hardcodes git write tools; it does not start github_inline_comment unless mcp__github_inline_comment__* or mcp__github__* is allowlisted. All of that needs a writable forge token in the model job. This workflow splits those: the model job’s GITHUB_TOKEN has no write scopes, and the posting job submits the summary after the model finishes. The input is kept so existing callers do not fail at the call gate. |
show-full-output |
boolean | false |
Show the full JSON output from Claude Code. Turn it on to diagnose a review that fails inside the model call. |
apt-packages |
string | '' |
Space-separated system packages to apt-get install before the review runs (e.g. maxima for a computer algebra system). Empty to skip. |
pip-packages |
string | '' |
Space-separated pip packages to install with pip3 --break-system-packages before the review runs (e.g. sympy). Empty to skip. |
lab-manual |
boolean | true |
Hold the PR to the SERG lab manual (coding style/practices, repository conventions) during review, in addition to correctness. Default true; set false for repos where the R/tidyverse/Quarto-focused manual doesn’t apply (e.g. a GitHub Actions, shell, or docs repo), so the reviewer isn’t handed guidance irrelevant to the diff. |
check-latex-macros |
boolean | false |
Check PR diffs for LaTeX math simplifiable via an existing macro from the d-morrison/macros submodule, and for nontrivial expressions repeated 3+ times that are candidates for a new one. Off by default. The macro checks read definitions only from a locally checked-out submodule (the repo’s pinned version is authoritative), so this only finds real macro definitions when the repo vendors the d-morrison/macros submodule and checkout-submodules: true is also set. |
report-cost |
boolean | true |
Post a follow-up PR comment with the review’s dollar cost (total_cost_usd, summed across the initial attempt and any stub-review retry). See README.md’s feature-parity table for the upstream source citation. Set false to suppress it. |
model |
string | '' |
Model for the reviewer to run on, passed through as the Claude Code CLI’s --model flag (e.g. claude-opus-4-8). Empty (default) falls through to claude-code-action’s own default. Unlike claude.yml, this workflow has no generic claude-args passthrough, so this is the only way to influence the model. |
use-ai-config |
boolean | true |
Install the Morrison-Lab/ai-config plugin (ai-config@Morrison-Lab) alongside the built-in code-review@claude-code-plugins, so the reviewer applies the lab’s shared review conventions rather than only the calling repo’s own CLAUDE.md. Set false to opt out. |
plugin-marketplaces |
string | '' |
Extra newline-separated plugin marketplace Git URLs, added on top of the two this workflow already uses (anthropics/claude-code.git for the code-review plugin, and Morrison-Lab/ai-config.git unless use-ai-config is false). Pair with plugins below; empty adds none. |
plugins |
string | '' |
Extra newline-separated plugin refs to install (<plugin>@<marketplace-name>), added on top of the built-in code-review@claude-code-plugins and ai-config@Morrison-Lab. Each marketplace name must match the name that marketplace declares in its own .claude-plugin/marketplace.json, not the URL given in plugin-marketplaces. |
extra-secret-names |
string | '' |
Space-, comma-, or newline-separated caller secret names to export into the reviewer environment (e.g. 'EPI202_TOKEN EPI204_TOKEN'). Requires secrets: inherit on the caller uses:. Secret names must match [A-Z_][A-Z0-9_]* and cannot shadow protected workflow environment variables. Empty to skip. |
Secrets
| Secret | Required | Description |
|---|---|---|
CLAUDE_CODE_OAUTH_TOKEN |
no | OAuth token for Claude Code (Claude Max plan). |
ANTHROPIC_API_KEY |
no | Direct Anthropic API key (or GitHub App via /install-github-app), used instead of CLAUDE_CODE_OAUTH_TOKEN. |
SUBMODULES_TOKEN |
no | Read access to private submodules. |
Permissions
Grant contents: read, pull-requests: write, issues: write, actions: read, and add either the CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY secret. The model job’s GITHUB_TOKEN has no write scopes (contents / pull-requests / issues / actions: read); write is confined to jobs that never run the model (gather-context stashes reviewers; post-review downloads the review artifact and comments) (gha#580). actions: read is required on the caller: a permissions: block sets unspecified scopes to none, and post-review needs it to download the packed artifact (the model job also uses it for the github_ci MCP server).
The require-review gate
Put review / require-review in branch protection’s required checks, not review / claude-review: the review job reports success in cases where no verdict was produced, and this gate job exists to distinguish them.
Read its result as follows.
| Result | What it attests |
|---|---|
| Green | A reviewer ran and the job did not fail. Not that the reviewer approved — read the posted verdict for that. |
| Gray (skipped) | No review ran. Does not block merging. |
| Red | The review job did not succeed. Blocks merging. |
The gate skips gray whenever no review was attempted. That covers anything that made the claude-review job’s own if: false — a draft, fork, or bot-authored PR, a dispatch blocked by the fork/Dependabot guard, or the context-gathering job having been cancelled (or, on a dispatched review, failed — an ordinary PR event keeps its event-pinned head, so a failed context-gather does not suppress its review) — plus a claude-review run canceled by a newer run of the same PR (cancel-in-progress, #585): the superseded run’s gate skips gray rather than going red, and the surviving run’s review is the one to read. The gate also skips gray when the posting job reported the review stale — the PR head moved between the review and the post, so no comment was posted and the next head’s own run carries the verdict. Two further cases where the job ran and stopped early also skip gray:
- the API quota was exhausted — either before any review work happened, or part-way through the run, which leaves a
429behind after real turns and real cost (#520); - restoring default-branch copies of
.github/workflows/failed, so the review was skipped rather than executing workflow YAML from the PR head (#598).
PRs that edit workflow files are reviewed when that restore succeeds. The job replaces .github/workflows/ with the default-branch tree after checkout and passes github_token so claude-code-action skips its OIDC workflow-content check. On a dispatched review it also omits --ref, so GitHub executes the default-branch caller rather than the PR head’s YAML. A files-list response shorter than the PR’s changed_files count is treated the same as a failed listing (GitHub caps that endpoint at 3000 files), so a large PR cannot hide a workflow edit behind the cap. Check-runs for that no---ref dispatch land on the default branch (#285); the review comment still posts on the PR.
A gray skip here still means no review ran. Do not read it as an all-clear. A later re-run can recover if fetching the default branch was the problem.
The require-clean-verdict gate
Where require-review verifies review delivery (the review ran and was posted), require-clean-verdict acts as an opt-in server-side verdict gate (gha#767). Put review / require-clean-verdict in branch protection’s required checks to enforce that only PRs with an affirmatively clean AI review verdict (“Ready for merge”, “Clean”, “Approved”) can merge.
| Result | What it attests |
|---|---|
| Green | The review completed, was posted for the current PR head, and produced an affirmatively clean verdict (“Ready for merge”). |
| Gray (skipped) | No review ran (draft, fork, bot author, quota exhausted, self-mod skip, cancellation, or stale head). Does not block merging. |
| Red | The review failed or produced a non-clean verdict (“Needs more work”, “Changes requested”, “Blocked”, or unaddressed findings). Blocks merging when configured as a required check. |
Concurrency
Do not declare a top-level concurrency: block in your caller workflow. claude-code-review.yml manages per-PR concurrency internally on its claude-review job (group: claude-review-<PR>). Adding a top-level concurrency: block in the caller with a PR-scoped group name causes GitHub Actions to detect a deadlock between the top-level workflow and the nested job, cancelling the run immediately (gha#437).
Example
# Copy to .github/workflows/claude-code-review.yml in your repo.
# NOTE: requires either CLAUDE_CODE_OAUTH_TOKEN (Claude Max plan) or
# ANTHROPIC_API_KEY (direct API / GitHub App via /install-github-app). Pass
# secrets explicitly (below) rather than via `secrets: inherit`: GitHub only
# inherits secrets into a reusable workflow owned by the same org/user, so a
# cross-owner caller (e.g. a UCD-SERG-org repo calling this d-morrison
# user-owned workflow) inherits an empty token and the run fails env-validation.
# Default: mention-triggered only. claude.yml re-dispatches this workflow on an
# `@claude review` mention (and after an @claude run pushes commits) via
# workflow_dispatch. Keep the file named claude-code-review.yml (or set
# claude.yml's review-workflow-file to match) so the dispatch resolves.
# Optional: uncomment pull_request for automatic review, and uncomment
# issue_comment to allow trusted collaborators to trigger `/review` directly.
# If you have DISABLED claude.yml, do BOTH of these, or nothing happens: uncomment
# the issue_comment trigger below, and set the repository variable
# CLAUDE_AGENT_DISABLED to `true`. dispatch-on-comment only ever runs on an
# issue_comment event, so the variable on its own changes nothing.
name: Claude Code Review
on:
# Optional: enable automatic review on PR activity.
# pull_request:
# types: [opened, synchronize, ready_for_review, reopened]
# Optional: direct on-demand `/review` path (independent of @claude), plus
# the opt-in `@claude review` fallback for repos with the agent disabled.
# issue_comment:
# types: [created]
workflow_dispatch:
inputs:
pr_number:
description: "Pull request number to review"
required: true
type: string
jobs:
review:
permissions:
contents: read
pull-requests: write
issues: write
actions: read
uses: Morrison-Lab/gha/.github/workflows/claude-code-review.yml@v2
secrets:
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} # Max-plan OAuth; empty when using API key
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} # direct API key; empty when using OAuth
SUBMODULES_TOKEN: ${{ secrets.SUBMODULES_TOKEN }} # optional; empty when unset
with:
# Wires the workflow_dispatch input through so claude.yml can re-dispatch
# a review on Claude's commits; empty (and ignored) on pull_request runs.
pr-number: ${{ inputs.pr_number }}
# with:
# checkout-submodules: true # SUBMODULES_TOKEN secret only for private submodules
# allowed-bots: 'github-actions[bot],claude' # accept more bot actors (default: github-actions[bot])
# track-progress: true # ignored (gha#580); tag mode needs a writable token in the model job
# lab-manual: false # skip the R/tidyverse SERG lab manual (default true; set false for non-R/Quarto repos)
# apt-packages: maxima # system packages for the reviewer's Bash tool, e.g. a CAS
# pip-packages: sympy # pip packages, e.g. a computer-algebra system for math-heavy repos
# check-latex-macros: true # flag LaTeX simplifiable via d-morrison/macros; also needs checkout-submodules: true
# report-cost: false # suppress the dollar-cost comment (default true)
# model: claude-opus-4-8 # override the reviewer's model (default: claude-code-action's own default)
# use-ai-config: false # skip the Morrison-Lab/ai-config plugin (installed by default)
# plugin-marketplaces: https://github.com/<owner>/<repo>.git # further plugin sources
# plugins: <plugin>@<marketplace-name>
# extra-secret-names: EPI202_TOKEN EPI204_TOKEN # export caller secrets to reviewer env (needs secrets: inherit)
# prompt-addendum: |
# Repo-specific review guidance (e.g. Quarto/R conventions to enforce).See examples/claude-code-review.yml for the full caller stub.