Skip to content

Commit 04c1943

Browse files
authored
Merge pull request #181 from Morrison-Lab/claude/issue-178-migrate-review-workflow
Migrate claude-code-review.yml to the Morrison-Lab/gha reusable workflow
2 parents bc784b6 + 19db021 commit 04c1943

1 file changed

Lines changed: 67 additions & 158 deletions

File tree

Lines changed: 67 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -1,177 +1,86 @@
1-
# Automatic Claude Code review of pull requests.
1+
# Automatic Claude Code review of pull requests -- thin caller of the central
2+
# Morrison-Lab/gha reusable review workflow.
23
#
3-
# Runs the upstream `code-review@claude-code-plugins` plugin so we pick up
4-
# any improvements to the canonical review skill, then layers an
5-
# R-package-specific addendum on top.
4+
# Migrated from a bespoke anthropics/claude-code-action@v1 workflow (see #178)
5+
# so rpt inherits the upstream hardening automatically as @v2 slides:
6+
# stub-review retry, the is_error/verdict guard, prior-review-context dedup,
7+
# older-comment collapse, the cost comment, and gha#400's duplicate-review
8+
# fix. The reviewer stash/restore dance is handled inside the reusable
9+
# workflow too: all requested reviewers (human and AI, e.g. Copilot) are
10+
# cleared while Claude reviews and re-requested when it finishes, which
11+
# generalizes the d-morrison-only toggle this file used to carry.
612
#
7-
# Each run posts a fresh review comment; prior reviews are left in place
8-
# so the PR keeps a visible history rather than a rolling sticky.
13+
# The reusable workflow runs in AGENT MODE (track-progress: false), so reviews
14+
# are summary-only: the upstream inline-comment tool is not initialized in
15+
# agent mode (anthropics/claude-code-action#635), and tag mode is not safe
16+
# until anthropics/claude-code-action#1415 (a read_only input) ships. Set
17+
# track-progress: true here once one of those lands to restore inline comments.
918
#
10-
# Skips drafts, Dependabot bumps, and fork PRs (fork PRs can't read repo
11-
# secrets). Project guidance is in CLAUDE.md. Requires the
12-
# CLAUDE_CODE_OAUTH_TOKEN repository secret.
13-
19+
# Keep this file named claude-code-review.yml and keep the workflow_dispatch
20+
# pr_number input: claude.yml re-dispatches a review with
21+
# `gh workflow run claude-code-review.yml -f pr_number=<n>` after an @claude
22+
# run pushes commits. Requires the CLAUDE_CODE_OAUTH_TOKEN repository secret.
23+
# See Morrison-Lab/gha examples/claude-code-review.yml for the upstream stub.
1424
name: Claude Code Review
1525

1626
on:
1727
pull_request:
1828
types: [opened, synchronize, ready_for_review, reopened]
19-
# Allow manual re-review from the Actions UI (e.g. after an @claude run
20-
# pushes commits). GITHUB_TOKEN pushes don't fire `synchronize`, so a
21-
# manual dispatch path is useful.
29+
# Lets claude.yml re-dispatch a fresh review after an @claude run pushes
30+
# commits (GITHUB_TOKEN pushes do not fire `synchronize`), and allows a
31+
# manual re-review from the Actions UI.
2232
workflow_dispatch:
2333
inputs:
2434
pr_number:
2535
description: 'Pull request number to review'
2636
required: true
27-
type: number
28-
29-
# Serialize reviews per PR: a newer push cancels the in-progress review
30-
# of the now-stale diff so only the freshest review runs (and posts a
31-
# comment). Plain `cancel-in-progress: true` is safe here — this workflow
32-
# is read-only (its allowedTools grant no git push / commit), so it never
33-
# pushes a fix.
34-
# `|| inputs.pr_number` keeps a workflow_dispatch review in the same
35-
# group as the PR's pull_request reviews so they dedupe.
36-
concurrency:
37-
group: claude-review-${{ github.event.pull_request.number || inputs.pr_number }}
38-
cancel-in-progress: true
37+
# String, not number: the reusable workflow's `pr-number` input is a
38+
# string and `gh workflow run -f` sends one.
39+
type: string
3940

4041
jobs:
41-
claude-review:
42-
# workflow_dispatch is a manual re-review from the Actions UI, so always
43-
# run it — this intentionally bypasses the draft/Dependabot/fork guard
44-
# below. A human with write access explicitly requesting a review can
45-
# review any PR. For pull_request events, skip drafts, Dependabot bumps,
46-
# and fork PRs (fork PRs can't read repo secrets via pull_request events,
47-
# so CLAUDE_CODE_OAUTH_TOKEN would be empty and the run would fail with a
48-
# noisy red check).
49-
if: |
50-
github.event_name == 'workflow_dispatch' ||
51-
(github.event.pull_request.draft == false &&
52-
github.event.pull_request.user.login != 'dependabot[bot]' &&
53-
github.event.pull_request.head.repo.full_name == github.repository)
54-
runs-on: ubuntu-latest
55-
timeout-minutes: 50
56-
env:
57-
# Resolve the PR number once: from the pull_request event, or the
58-
# workflow_dispatch input when a manual re-review is triggered.
59-
PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }}
42+
review:
6043
permissions:
6144
contents: read
6245
pull-requests: write
63-
issues: read
46+
issues: write
6447
id-token: write
65-
66-
steps:
67-
# A PR that changes THIS workflow file can't be reviewed by the action:
68-
# its App-token exchange requires the review workflow to match the
69-
# default branch, so it 401s — the action itself says this is normal
70-
# and to ignore it. Rather than post a failing check, detect that case
71-
# and skip the review; it runs normally once merged.
72-
- name: Skip self-review when the PR edits this workflow
73-
id: selfmod
74-
env:
75-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
76-
REPO: ${{ github.repository }}
77-
run: |
78-
files=$(gh api "repos/$REPO/pulls/$PR_NUMBER/files" --paginate --jq '.[].filename' 2>/dev/null || true)
79-
if printf '%s\n' "$files" | grep -qxF '.github/workflows/claude-code-review.yml'; then
80-
echo "self_mod=true" >> "$GITHUB_OUTPUT"
81-
echo "::notice::PR #$PR_NUMBER edits claude-code-review.yml — skipping self-review (runs after merge)."
82-
else
83-
echo "self_mod=false" >> "$GITHUB_OUTPUT"
84-
fi
85-
86-
- name: Checkout repository
87-
uses: actions/checkout@v7
88-
with:
89-
fetch-depth: 1
90-
91-
# Skip reviewer toggling on fork PRs: GitHub downgrades GITHUB_TOKEN
92-
# to read-only for fork events even when `pull-requests: write` is
93-
# declared, so the DELETE / POST below would 403. The job-level
94-
# reviewer (Run Claude Code Review) still runs because it uses its
95-
# own OAuth token.
96-
- name: Remove review request from d-morrison while Claude is reviewing
97-
id: remove_reviewer
98-
if: |
99-
github.event.pull_request.number &&
100-
github.event.pull_request.head.repo.full_name == github.repository
101-
env:
102-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
103-
HAD_REVIEWER: ${{ contains(github.event.pull_request.requested_reviewers.*.login, 'd-morrison') }}
104-
run: |
105-
echo "had_reviewer=$HAD_REVIEWER" >> "$GITHUB_OUTPUT"
106-
if [ "$HAD_REVIEWER" = "true" ]; then
107-
gh api -X DELETE \
108-
"repos/${{ github.repository }}/pulls/$PR_NUMBER/requested_reviewers" \
109-
-f "reviewers[]=d-morrison" \
110-
|| echo "::warning::failed to remove d-morrison from reviewers on PR #$PR_NUMBER"
111-
fi
112-
113-
- name: Run Claude Code Review
114-
id: claude-review
115-
if: steps.selfmod.outputs.self_mod != 'true'
116-
uses: anthropics/claude-code-action@v1
117-
with:
118-
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
119-
# A `claude` remote session pushing a commit to a PR fires
120-
# `synchronize` with the `claude` bot as actor; another CI
121-
# workflow pushing a commit fires it as `github-actions[bot]`.
122-
# Without allowing these bots the action aborts with "Workflow
123-
# initiated by non-human actor" and posts no review. The job
124-
# `if:` already restricts runs to same-repo, non-Dependabot PRs,
125-
# so accepting these bots here is safe.
126-
allowed_bots: "github-actions[bot],claude"
127-
plugin_marketplaces: 'https://github.com/anthropics/claude-code.git'
128-
plugins: 'code-review@claude-code-plugins'
129-
track_progress: ${{ github.event_name == 'pull_request' && 'true' || 'false' }}
130-
prompt: |
131-
/code-review:code-review ${{ github.repository }}/pull/${{ env.PR_NUMBER }}
132-
133-
In addition to the standard checks above, this is an R package
134-
template following UCD-SERG standards, so also prioritize:
135-
136-
1. **R package correctness**
137-
- Roxygen2 docs are in sync (`devtools::document()` is required
138-
before committing; `R-check-docs.yml` enforces this).
139-
- `NAMESPACE` and `man/` are not edited by hand.
140-
- `README.md` is generated from `README.Rmd`; edits go there.
141-
142-
2. **R style** (`.lintr.R` is authoritative)
143-
- snake_case names, line length ≤ 80 chars, no `T`/`F` for
144-
TRUE/FALSE, no `:::`-style internal calls.
145-
- Tidyverse idioms and native `|>` pipe.
146-
147-
3. **Testing**
148-
- New/changed behaviour is covered by testthat.
149-
- Random outputs use `set.seed()` so snapshots are deterministic.
150-
151-
4. **Changelog**
152-
- Every user-facing change has a `NEWS.md` bullet
153-
(`news.yaml` enforces this; a missing entry is a CI failure).
154-
155-
5. **CI hygiene**
156-
- No new dependencies without a `DESCRIPTION` entry.
157-
- Spell-check / lint failures are fixed at the source,
158-
not suppressed.
159-
160-
**Post line-specific findings as inline review comments** anchored
161-
to the relevant line(s). Reserve the top-level summary for a brief
162-
overall verdict plus any finding not tied to a specific line;
163-
don't restate each inline comment there. Skip generic praise.
164-
165-
- name: Re-request review from d-morrison when Claude finishes reviewing
166-
if: |
167-
always() &&
168-
github.event.pull_request.number &&
169-
github.event.pull_request.head.repo.full_name == github.repository &&
170-
steps.remove_reviewer.outputs.had_reviewer == 'true'
171-
env:
172-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
173-
run: |
174-
gh api -X POST \
175-
"repos/${{ github.repository }}/pulls/$PR_NUMBER/requested_reviewers" \
176-
-f "reviewers[]=d-morrison" \
177-
|| echo "::warning::failed to re-request d-morrison as reviewer on PR #$PR_NUMBER"
48+
actions: read # lets the reviewer read CI status (github_ci MCP server)
49+
uses: Morrison-Lab/gha/.github/workflows/claude-code-review.yml@v2
50+
secrets:
51+
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
52+
with:
53+
# Wire the workflow_dispatch input through so claude.yml can re-dispatch
54+
# a review on Claude's commits; empty (and ignored) for pull_request runs.
55+
pr-number: ${{ inputs.pr_number }}
56+
# Accept the `claude` and github-actions[bot] actors so a review
57+
# dispatched after an @claude run (or another CI push) is not rejected as
58+
# "non-human". Matches the pre-migration workflow's allowed_bots.
59+
allowed-bots: "github-actions[bot],claude"
60+
prompt-addendum: |
61+
This is an R package template following UCD-SERG standards, so in
62+
addition to the standard checks also prioritize:
63+
64+
1. **R package correctness**
65+
- Roxygen2 docs are in sync (`devtools::document()` is required
66+
before committing; `R-check-docs.yml` enforces this).
67+
- `NAMESPACE` and `man/` are not edited by hand.
68+
- `README.md` is generated from `README.Rmd`; edits go there.
69+
70+
2. **R style** (`.lintr.R` is authoritative)
71+
- snake_case names, line length <= 80 chars, no `T`/`F` for
72+
TRUE/FALSE, no `:::`-style internal calls.
73+
- Tidyverse idioms and native `|>` pipe.
74+
75+
3. **Testing**
76+
- New/changed behaviour is covered by testthat.
77+
- Random outputs use `set.seed()` so snapshots are deterministic.
78+
79+
4. **Changelog**
80+
- Every user-facing change has a `NEWS.md` bullet
81+
(`news.yaml` enforces this; a missing entry is a CI failure).
82+
83+
5. **CI hygiene**
84+
- No new dependencies without a `DESCRIPTION` entry.
85+
- Spell-check / lint failures are fixed at the source,
86+
not suppressed.

0 commit comments

Comments
 (0)