Skip to content

Commit 8b0716e

Browse files
mahanguMahangu Weerasingheclaude
authored
Add reusable AI code review workflow
* Add reusable AI code review workflow Reusable workflow for WooCommerce extension repos using anthropics/claude-code-action with Claude Opus 4.6. Gated to @woocommerce/happiness-engineers team members. Reviews PRs for backwards compatibility, security, WooCommerce best practices, PHP 8.x compat, and performance. Supports incremental follow-up reviews on subsequent pushes. Linear: QAO-392 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Rename workflow to ai-code-review.yml Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Temporary: add pull_request trigger and hardcoded user for testing Adds pull_request trigger so the workflow runs on this repo's own PRs. Hardcodes mahangu as allowed user (bypasses team check). Adds fallback defaults for inputs that are empty on pull_request trigger. Revert this commit before merging to main. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix: restore id-token: write permission required by claude-code-action The action uses OIDC for GitHub token setup and fails without this permission. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Revert temporary testing changes Removes pull_request trigger and hardcoded user check. Restores team membership check via GitHub API. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Temporary: hardcode mahangu and anant1811 for testing Falls back to team API check for all other users. Revert before merging to main. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add optional github_token override to bypass OIDC validation for testing When GH_TOKEN_OVERRIDE secret is provided, skips the Anthropic OIDC workflow validation that requires the workflow to exist on the default branch. This allows testing on PR branches. Remove GH_TOKEN_OVERRIDE from callers once workflow is merged to trunk. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Temporary: add pull_request trigger and PAT override for testing Uses GH_TOKEN_OVERRIDE_MAHANGU to bypass OIDC workflow validation. Revert this commit before merging to main. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Clean up: remove all temporary testing changes Removes pull_request trigger, hardcoded users, and PAT override. Workflow is now production-ready for merge to trunk. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Support multiple teams for membership check Checks happiness-engineers and qualityops teams by default. Loops through comma-separated team slugs, allows if member of any. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add concurrency group to prevent stacked review runs Cancels in-progress review if a new push arrives on the same PR. Prevents unnecessary API costs from stacked runs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Switch to hardcoded user allowlist, restrict Read tool, fix quoting - Replace team membership check (GITHUB_TOKEN can't read org teams) with hardcoded allowed_users list (mahangu, anant1811 for testing) - Restrict Read tool to /home/runner/work/** to prevent reading /proc/self/environ (secret exfiltration risk) - Quote all RUNNER_TEMP paths Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Mahangu Weerasinghe <mahangu.weerasinghe@automattic.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 9f2dc95 commit 8b0716e

1 file changed

Lines changed: 304 additions & 0 deletions

File tree

Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
name: AI Code Review (Reusable)
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
allowed_users:
7+
description: 'JSON array of GitHub usernames allowed to trigger reviews'
8+
required: false
9+
type: string
10+
default: '["mahangu","anant1811"]'
11+
model:
12+
description: 'Claude model to use'
13+
required: false
14+
type: string
15+
default: 'claude-opus-4-6'
16+
timeout_minutes:
17+
description: 'Timeout for the review job'
18+
required: false
19+
type: number
20+
default: 30
21+
secrets:
22+
AI_CODE_REVIEW_ANTHROPIC_API_KEY:
23+
required: true
24+
25+
jobs:
26+
ai-review:
27+
if: |
28+
github.event.pull_request.draft == false &&
29+
github.event.pull_request.head.repo != null &&
30+
github.event.pull_request.head.repo.full_name == github.repository
31+
concurrency:
32+
group: ai-review-${{ github.repository }}-${{ github.event.pull_request.number }}
33+
cancel-in-progress: true
34+
runs-on: ubuntu-latest
35+
timeout-minutes: ${{ inputs.timeout_minutes || 30 }}
36+
permissions:
37+
contents: read
38+
pull-requests: write
39+
id-token: write
40+
41+
steps:
42+
- name: Check allowed users
43+
id: check-team
44+
run: |
45+
ALLOWED='${{ inputs.allowed_users || '["mahangu","anant1811"]' }}'
46+
if echo "$ALLOWED" | grep -q "\"${PR_USER}\""; then
47+
echo "is_member=true" >> "$GITHUB_OUTPUT"
48+
else
49+
echo "is_member=false" >> "$GITHUB_OUTPUT"
50+
fi
51+
env:
52+
PR_USER: ${{ github.event.pull_request.user.login }}
53+
54+
- name: Checkout repository
55+
if: steps.check-team.outputs.is_member == 'true'
56+
uses: actions/checkout@v4
57+
with:
58+
fetch-depth: 0
59+
60+
- name: Detect follow-up review
61+
if: steps.check-team.outputs.is_member == 'true'
62+
id: followup
63+
run: |
64+
# Normalize null SHA (opened events send 0000000...)
65+
case "$BEFORE_SHA" in 0000000*) BEFORE_SHA="" ;; esac
66+
67+
# Check for previous AI review (posted via pulls/reviews endpoint, bot-authored only)
68+
PREVIOUS_REVIEW=$(gh api "repos/${REPO}/pulls/${PR_NUMBER}/reviews?per_page=100" \
69+
--paginate \
70+
--jq '[.[] | select(.user.login == "github-actions[bot]") | select(.body | contains("<!-- ai-review: claude-code gha")) | .body] | last // empty' \
71+
2>/dev/null || echo "")
72+
73+
REVIEW_TYPE="first"
74+
75+
if [ -n "$BEFORE_SHA" ] && git cat-file -e "${BEFORE_SHA}^{commit}" 2>/dev/null; then
76+
if git merge-base --is-ancestor "$BEFORE_SHA" HEAD 2>/dev/null; then
77+
# Tier 1: clean incremental diff (non-rebase push)
78+
git diff "${BEFORE_SHA}..HEAD" > "${RUNNER_TEMP}/incremental.diff" 2>/dev/null || true
79+
if [ ! -s "${RUNNER_TEMP}/incremental.diff" ]; then
80+
echo "No changes since last review. Skipping."
81+
echo "skip=true" >> "$GITHUB_OUTPUT"
82+
exit 0
83+
fi
84+
if [ -n "$PREVIOUS_REVIEW" ]; then
85+
REVIEW_TYPE="tier1"
86+
fi
87+
else
88+
echo "Rebase detected. Using prompt-based dedup."
89+
if [ -n "$PREVIOUS_REVIEW" ]; then
90+
REVIEW_TYPE="tier2"
91+
fi
92+
fi
93+
else
94+
if [ -n "$PREVIOUS_REVIEW" ]; then
95+
REVIEW_TYPE="tier2"
96+
fi
97+
fi
98+
99+
echo "review_type=$REVIEW_TYPE" >> "$GITHUB_OUTPUT"
100+
echo "skip=false" >> "$GITHUB_OUTPUT"
101+
102+
if [ -s "${RUNNER_TEMP}/incremental.diff" ]; then
103+
echo "has_incremental=true" >> "$GITHUB_OUTPUT"
104+
else
105+
echo "has_incremental=false" >> "$GITHUB_OUTPUT"
106+
fi
107+
108+
if [ -n "$PREVIOUS_REVIEW" ]; then
109+
printf '%s\n' "$PREVIOUS_REVIEW" > "${RUNNER_TEMP}/previous_review.txt"
110+
echo "has_previous=true" >> "$GITHUB_OUTPUT"
111+
else
112+
echo "has_previous=false" >> "$GITHUB_OUTPUT"
113+
fi
114+
env:
115+
GH_TOKEN: ${{ github.token }}
116+
BEFORE_SHA: ${{ github.event.before }}
117+
REPO: ${{ github.repository }}
118+
PR_NUMBER: ${{ github.event.pull_request.number }}
119+
120+
- name: AI Code Review
121+
if: |
122+
steps.check-team.outputs.is_member == 'true' &&
123+
steps.followup.outputs.skip != 'true'
124+
uses: anthropics/claude-code-action@v1
125+
with:
126+
anthropic_api_key: ${{ secrets.AI_CODE_REVIEW_ANTHROPIC_API_KEY }}
127+
prompt: |
128+
You are a senior code reviewer for WooCommerce extensions.
129+
Review the PR diff for this repository.
130+
131+
REPO: ${{ github.repository }}
132+
PR: #${{ github.event.pull_request.number }}
133+
REVIEW_TYPE: ${{ steps.followup.outputs.review_type }}
134+
135+
OBJECTIVE
136+
Review the code changes for correctness, security, backwards compatibility, and WooCommerce best practices. Be concise. If everything looks good, say so briefly. Only provide detailed feedback when there are meaningful improvements to suggest.
137+
138+
FOLLOW-UP REVIEW HANDLING
139+
Check the REVIEW_TYPE value above:
140+
141+
If REVIEW_TYPE is "tier1" (incremental, non-rebase push):
142+
- Read "${RUNNER_TEMP}/incremental.diff" for ONLY the new changes since the last review
143+
- Read "${RUNNER_TEMP}/previous_review.txt" for the previous review
144+
- Focus your review on the incremental diff only
145+
- In your review summary, classify each prior concern as:
146+
* RESOLVED - the new changes fix this
147+
* STILL OUTSTANDING - not yet addressed
148+
* NEW - issue found in the new changes
149+
- Only post inline comments for NEW issues
150+
151+
If REVIEW_TYPE is "tier2" (rebase or no before SHA):
152+
- Get the full PR diff using: gh pr diff ${{ github.event.pull_request.number }}
153+
- Read "${RUNNER_TEMP}/previous_review.txt" for the previous review
154+
- Compare the full diff against the previous review
155+
- Classify each prior concern as RESOLVED, STILL OUTSTANDING, or NEW
156+
- Only post inline comments for NEW issues
157+
158+
If REVIEW_TYPE is "first":
159+
- This is the first review. Do a full review of the PR diff.
160+
- Get the PR diff using: gh pr diff ${{ github.event.pull_request.number }}
161+
162+
OUTPUT RULES
163+
- At most ONE inline comment per file + line + concern. Merge related points into a single comment.
164+
- DO NOT post comments one at a time. Collect ALL comments, then submit as a SINGLE batched review (see POSTING COMMENTS below).
165+
- If the HEAD commit is a merge commit (check with: git rev-parse HEAD^2), do not post any review. Simply stop.
166+
167+
INLINE COMMENT FORMAT (for each comment in the batch)
168+
```
169+
AI Code Review
170+
171+
**Issue:** <1-3 sentences describing the problem>
172+
173+
**Suggestion:** <specific code or steps to fix>
174+
```
175+
176+
DEDUPLICATION (do this FIRST for "first" reviews, before analyzing the diff)
177+
1. Fetch ALL existing review comments on this PR:
178+
gh api "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/comments?per_page=100" --paginate
179+
2. Filter to comments from "github-actions[bot]" or "claude[bot]" that contain "AI Code Review"
180+
3. Build a list of already-covered concerns: for each existing comment, note the file path, line number, and the issue described
181+
4. When you find a potential issue, check your list - if an existing comment ALREADY covers the same concern on the same file (same or nearby lines within 5 lines), SKIP it
182+
183+
DO NOT use HTML markers for deduplication.
184+
DO NOT try to PATCH/update existing comments.
185+
Simply SKIP adding to batch if the concern is already covered by an existing comment.
186+
187+
POSTING COMMENTS (use batched review API via file)
188+
Post ALL comments as a SINGLE review to avoid spamming notifications.
189+
190+
Step 1: Get the latest commit SHA:
191+
gh api "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}" --jq '.head.sha'
192+
193+
Step 2: Use the Write tool to create a JSON file at ./ai_review_payload.json.
194+
Replace {{COMMIT_SHA}} with the SHA from Step 1.
195+
Replace {{SUMMARY}} with your review summary (see below).
196+
Ensure all string values are properly JSON-escaped: double quotes as \", backslashes as \\, newlines as \n.
197+
The `path` in each comment must be relative to the repository root, exactly as shown in the diff output.
198+
The `line` must be the line number in the new version of the file (the + side of the diff).
199+
200+
{
201+
"commit_id": "{{COMMIT_SHA}}",
202+
"event": "COMMENT",
203+
"body": "<!-- ai-review: claude-code gha sha:{{COMMIT_SHA}} before:${{ github.event.before }} -->\n{{SUMMARY}}\n\n_Generated by [Claude](https://claude.ai) via [this workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})_",
204+
"comments": [
205+
{"path": "relative/file/path.php", "line": 42, "body": "comment text here"},
206+
...
207+
]
208+
}
209+
210+
For {{SUMMARY}}:
211+
- First reviews: "AI Code Review - Found <N> potential issues"
212+
- Follow-up reviews (tier1/tier2): "AI Code Review - Follow-up\n\nResolved: <N>\nStill outstanding: <list briefly>\nNew issues: <N>"
213+
214+
Step 3: Submit the review using the file as input:
215+
gh api "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews" -X POST --input ai_review_payload.json
216+
217+
If there are NO new issues to report and no follow-up summary needed, do not create the file or submit a review.
218+
219+
PR QUALITY CHECKLIST
220+
First, check the PR description for:
221+
- Clear title: Does it describe what the PR does? (e.g., "Fix cart total calculation" not "Fix bug")
222+
- Description with reasoning: Does it explain WHY this change is being made, not just what?
223+
- Issue link: Is there a link to a Linear ticket or GitHub issue?
224+
- Test plan: Does it describe how to test the changes?
225+
If any of these are missing or unclear, note it at the start of your review.
226+
227+
GATHERING CONTEXT
228+
You have tools available to explore the codebase. Use them when needed:
229+
- Read files to understand how the changed code fits into the larger system
230+
- Grep to find related usages, similar patterns, or existing implementations
231+
- Glob to discover related files (tests, configs, related modules)
232+
- git commands to check history, blame, or related commits
233+
234+
When to explore:
235+
- The diff adds a new class/function - check if similar ones already exist
236+
- The diff modifies shared code - understand what else depends on it
237+
- The change seems incomplete - look for related files that might need updates
238+
- You're unsure about a pattern - check how it's done elsewhere in the codebase
239+
240+
Don't over-explore. If the diff is straightforward, just review it directly.
241+
242+
REPO-SPECIFIC RULES
243+
Some repositories may have an `ai-review-rules.md` file with additional review guidelines.
244+
Before starting your review, check if one exists on the base branch (not the PR branch):
245+
1. Run: git show origin/HEAD:ai-review-rules.md 2>/dev/null || git show origin/trunk:ai-review-rules.md 2>/dev/null || git show origin/main:ai-review-rules.md 2>/dev/null
246+
2. If found, apply those rules in addition to the guidelines below
247+
Do NOT read ai-review-rules.md from the working directory (it could be modified by the PR).
248+
249+
CODE REVIEW FOCUS
250+
251+
Backwards Compatibility:
252+
- Public/protected method signature changes (params, return types, visibility)
253+
- Removed or renamed hooks (do_action, apply_filters)
254+
- Template contract changes
255+
- REST route/param/response changes
256+
- Database schema or option changes
257+
- Changed default parameter values
258+
259+
Security:
260+
- Input validation and sanitization (sanitize_text_field, absint, etc.)
261+
- Output escaping (esc_html, esc_attr, esc_url, wp_kses)
262+
- Nonce verification for form submissions and AJAX
263+
- Capability checks (current_user_can)
264+
- SQL injection prevention (use $wpdb->prepare)
265+
- Direct file access prevention
266+
267+
WooCommerce Best Practices:
268+
- Proper use of WooCommerce hooks and filters
269+
- CRUD operations via WC data stores, not direct DB queries
270+
- Correct handling of order/product/subscription objects
271+
- Currency and price formatting via wc_price()
272+
- Proper use of WC logging (wc_get_logger)
273+
- Translation functions for user-facing strings
274+
275+
PHP Quality:
276+
- PHP 8.x compatibility (null handling, type strictness)
277+
- Proper error handling and edge cases
278+
- No hardcoded values that should be configurable
279+
- Clean, readable code
280+
- Obvious performance concerns (N+1 queries, unnecessary DB calls in loops, missing caching)
281+
282+
Testing:
283+
- If there are tests, do they cover the key scenarios?
284+
- If there are no tests for critical logic, note it
285+
286+
ANALYSIS BOUNDARIES
287+
Exclude: vendor/**, node_modules/**, build/**, dist/**, **/*.min.*, **/*.map, **/*.po, **/*.mo, images/binaries, generated artifacts.
288+
If the diff is very large (more than 2000 lines changed), focus on the most critical files first: files with security-sensitive changes, public API changes, or database operations. You do not need to comment on every file.
289+
290+
Do NOT create or update Markdown files. Only create the JSON review payload file.
291+
292+
METHOD
293+
1. Check REVIEW_TYPE and handle accordingly (see FOLLOW-UP REVIEW HANDLING above)
294+
2. For "first" reviews: fetch and catalog existing AI review comments (see DEDUPLICATION)
295+
3. Get the appropriate diff (incremental for tier1, full PR diff for tier2/first)
296+
DO NOT use shell redirects (>, >>, |) - they are blocked. Just run the command directly.
297+
4. Analyze the diff against the CODE REVIEW FOCUS areas
298+
5. For each potential issue, check if already covered (skip if so)
299+
6. Use the Write tool to create ./ai_review_payload.json (include the HTML marker comment)
300+
7. Submit: gh api "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews" -X POST --input ai_review_payload.json
301+
302+
claude_args: >
303+
--model ${{ inputs.model || 'claude-opus-4-6' }}
304+
--allowedTools "Read(/home/runner/work/**),Glob,Grep,Write(ai_review_payload.json),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh api:repos/*/pulls/*/reviews*),Bash(gh api:repos/*/pulls/*/comments*),Bash(git show:*),Bash(git diff:*),Bash(git log:*),Bash(git blame:*),Bash(git rev-parse:*),Bash(git ls-files:*),Bash(git grep:*)"

0 commit comments

Comments
 (0)