Skip to content

Commit 5d718f1

Browse files
committed
feat: convert review workflow to dispatch to Henry
Replace direct claude-code-action execution with a thin dispatcher that sends review requests to nsheaps/.ai-agent-henry via repository_dispatch. Henry is now the source of truth for review execution, prompts, and check status reporting. The workflow retains trigger detection (PR events, label handling) and uses the review bot for authentication and dispatch. https://claude.ai/code/session_01JmF7PP9gnZXETH3Md77J2a
1 parent 0afe6c0 commit 5d718f1

1 file changed

Lines changed: 34 additions & 177 deletions

File tree

Lines changed: 34 additions & 177 deletions
Original file line numberDiff line numberDiff line change
@@ -1,213 +1,70 @@
11
name: Claude Code Review
22

3+
# Dispatches PR review requests to the Henry review agent (nsheaps/.ai-agent-henry).
4+
# Henry is the source of truth for review execution, prompts, and check status reporting.
5+
# This workflow handles trigger detection, label management, and dispatch only.
6+
37
on:
48
pull_request:
59
types: [opened, synchronize, ready_for_review, labeled]
6-
# Optional: Only run on specific file changes
7-
# paths:
8-
# - "src/**/*.ts"
9-
# - "src/**/*.tsx"
10-
# - "src/**/*.js"
11-
# - "src/**/*.jsx"
12-
# Don't run this when this file is modified in the PR since it will fail (for security reasons)
13-
# Sometimes claude will reject to review the PR if it sees this file changed (programatically) but it doesn't
14-
# seem consistent
15-
# paths-ignore:
16-
# - ".github/workflows/claude-code-review.yaml"
1710

1811
concurrency:
19-
group: ${{ github.event_name == 'pull_request' && format('claude-review-{0}', github.event.pull_request.number) || format('claude-review-{0}-{1}', github.ref, github.sha) }}
20-
# always let a previous review finish (so that time isn't wasted), but queued builds will still
21-
# be cancelled if a newer build is pushed
22-
# NOTE THIS IS NOT GUARANTEED AT THE JOB LEVEL
12+
group: claude-review-${{ github.event.pull_request.number }}
13+
# Let a previous dispatch finish before starting a new one.
14+
# Queued dispatches will still be cancelled if a newer push arrives.
2315
cancel-in-progress: false
2416

2517
jobs:
26-
claude-review:
18+
dispatch-review:
2719
# Triggers:
2820
# - Automatically on all open (non-draft) PRs
29-
# - On draft PRs only when 'request-review' label is added (label is removed after review starts)
30-
#
31-
# Logic: (not a draft) OR (labeled event with request-review)
21+
# - On draft PRs only when 'request-review' label is added (label is removed after dispatch)
3222
if: |
3323
github.event.pull_request.draft != true ||
34-
( github.event.action == 'labeled' && github.event.label.name == 'request-review')
24+
(github.event.action == 'labeled' && github.event.label.name == 'request-review')
3525
3626
runs-on: ubuntu-latest
3727
permissions:
38-
contents: write
28+
contents: read
3929
pull-requests: write
40-
issues: write
41-
id-token: write
42-
actions: read
4330

4431
steps:
45-
- name: Checkout and authenticate as GitHub App
32+
- name: Generate review bot token
4633
id: auth
47-
uses: nsheaps/github-actions/.github/actions/checkout-as-app@603052841cd49b9fdc99bc32979ff9c45c9b669e # checkout-as-app@main
34+
uses: actions/create-github-app-token@v2
4835
with:
4936
app-id: ${{ secrets.REVIEW_GITHUB_APP_ID }}
5037
private-key: ${{ secrets.REVIEW_GITHUB_APP_PRIVATE_KEY }}
51-
fetch-depth: '50'
38+
owner: nsheaps
5239

5340
- name: Remove request-review label
5441
if: github.event.action == 'labeled' && github.event.label.name == 'request-review'
5542
env:
5643
GH_TOKEN: ${{ steps.auth.outputs.token }}
5744
run: |
58-
gh pr edit ${{ github.event.pull_request.number }} --remove-label "request-review"
59-
60-
- name: Get additional job context
61-
uses: qoomon/actions--context@v5
62-
if: ${{ !cancelled() }}
63-
id: additional-context
64-
continue-on-error: true
45+
gh pr edit ${{ github.event.pull_request.number }} \
46+
--remove-label "request-review" \
47+
--repo "${{ github.repository }}"
6548
66-
- name: Ensure gh has the review extension to aid in getting review comments (not PR comments)
67-
# Used by claude to fetch PR review comments rather than direct API queries.
68-
# See https://github.com/cli/cli/discussions/3993
69-
# See https://github.com/cli/cli/issues/8409
70-
shell: bash
71-
run: |
72-
gh extension install agynio/gh-pr-review || echo "gh-pr-review extension already installed"
73-
74-
- name: Interpolate review prompt
75-
id: prompt
76-
uses: ./.github/actions/interpolate-prompt
77-
env:
78-
REPO: ${{ github.repository }}
79-
PR_NUMBER: ${{ github.event.pull_request.number }}
80-
JOB_CONTEXT: ${{ toJson(steps.additional-context.outputs) }}
49+
- name: Dispatch review to Henry
50+
uses: peter-evans/repository-dispatch@v4
8151
with:
82-
template-file: .github/prompts/claude-code-review.md
83-
84-
- name: Run Claude Code Review
85-
id: claude-review
86-
uses: anthropics/claude-code-action@v1
87-
env:
88-
GH_TOKEN: '${{ steps.auth.outputs.token }}'
89-
GITHUB_TOKEN: '${{ steps.auth.outputs.token }}'
90-
# These are in addition to any parsed settings.json files
91-
# Prefer this over claude_args, almost everything can be specified via settings json or env vars
92-
# While Bash is allowed, the list here is passed directly to the agent, so explicitly defining them
93-
# causes the agent to see those Bash commands as more relevant and will use them more often.
94-
# Ref: https://code.claude.com/docs/en/settings
95-
INPUT_SETTINGS_JSON: |
52+
token: ${{ steps.auth.outputs.token }}
53+
repository: nsheaps/.ai-agent-henry
54+
event-type: pull_request/review
55+
client-payload: |
9656
{
97-
"permissions": {
98-
"additionalDirectories": ["/tmp/"],
99-
"allow":[
100-
"Task",
101-
"Glob",
102-
"Grep",
103-
"LS",
104-
"ExitPlanMode",
105-
"Read",
106-
"Edit",
107-
"Write",
108-
"Replace",
109-
"MultiEdit",
110-
"NotebookRead",
111-
"NotebookEdit",
112-
"TodoWrite",
113-
"WebSearch",
114-
"WebFetch",
115-
"Bash",
116-
"BashOutput",
117-
"KillBash",
118-
"ListMcpResourcesTool",
119-
"ReadMcpResourceTool",
120-
"SlashCommand",
121-
"Skill",
122-
123-
"mcp__github__github_support_docs_search",
124-
"mcp__github__get_pull_request",
125-
"mcp__github__get_pull_request_diff",
126-
"mcp__github__get_pull_request_files",
127-
"mcp__github__get_pull_request_review_comments",
128-
"mcp__github__get_pull_request_reviews",
129-
"mcp__github__get_pull_request_status",
130-
"mcp__github__create_pending_pull_request_review",
131-
"mcp__github__add_comment_to_pending_review",
132-
"mcp__github__submit_pending_pull_request_review",
133-
134-
"Bash(git log:*)",
135-
"Bash(git diff:*)",
136-
"Bash(git status:*)",
137-
"Bash(git rev-parse:*)",
138-
"Bash(git fetch:*)",
139-
"Bash(git show:*)",
140-
"Bash(git merge:*)",
141-
"Bash(git remote get-url:*)",
142-
"Bash(git rm:*)",
143-
"Bash(gh issue view:*)",
144-
"Bash(gh search:*)",
145-
"Bash(gh issue list:*)",
146-
"Bash(gh pr comment:*)",
147-
"Bash(gh pr diff:*)",
148-
"Bash(gh pr view:*)",
149-
"Bash(gh pr-review:*)",
150-
"Bash(gh pr-review review view:*)",
151-
"Bash(gh pr list:*)",
152-
"Bash(gh run list:*)",
153-
"Bash(gh run view:*)",
154-
"Bash(gh extension list:*)"
155-
],
156-
"_denyComment": "It will wait for it's own check run to complete, not to mention it should review the code.",
157-
"deny": [
158-
"mcp__github_ci__get_ci_status",
159-
"mcp__github_ci__get_workflow_run_details",
160-
"mcp__github_ci__download_job_log",
161-
"Bash(gh pr checks:*)",
162-
"Bash(git push:*)"
163-
]
57+
"referer": "${{ github.repository }}/.github/workflows/claude-code-review.yaml",
58+
"source": {
59+
"repo": "${{ github.repository }}",
60+
"pr_number": ${{ github.event.pull_request.number }},
61+
"head_sha": "${{ github.event.pull_request.head.sha }}",
62+
"head_ref": "${{ github.event.pull_request.head.ref }}",
63+
"base_ref": "${{ github.event.pull_request.base.ref }}"
16464
},
165-
"model": "opus",
166-
"env": {
167-
"GH_TOKEN": "${{ steps.auth.outputs.token }}",
168-
"GITHUB_TOKEN": "${{ steps.auth.outputs.token }}",
169-
"_GH_PAGER_COMMENT": "disable gh from being able to treat the terminal as interactive",
170-
"GH_PAGER": "cat",
171-
"GH_PROMPT_DISABLED": "true",
172-
"_GH_BROWSER_COMMENT": "what app to open URLs with, since claude is not interactive, we use curl to pipe it to stdout",
173-
"GH_BROWSER": "curl",
174-
"CLAUDE_CODE_REMOTE": "true"
65+
"trigger": {
66+
"event": "pull_request",
67+
"action": "${{ github.event.action }}",
68+
"label": ${{ toJson(github.event.label.name || '') }}
17569
}
17670
}
177-
with:
178-
anthropic_api_key: ${{ secrets.REVIEW_ANTHROPIC_API_KEY || secrets.ANTHROPIC_API_KEY || '' }}
179-
claude_code_oauth_token: ${{ (secrets.REVIEW_ANTHROPIC_API_KEY || secrets.ANTHROPIC_API_KEY) && '' || secrets.CLAUDE_CODE_OAUTH_TOKEN }}
180-
# WARNING: PRINTS FULL JSON OUTPUT WHICH MAY INCLUDE SECRETS.
181-
# ONLY USE IN PRIVATE ENVIRONMENTS.
182-
show_full_output: true
183-
# bug: https://github.com/anthropics/claude-code-action/issues/759
184-
# won't find the past comments since it's looking for github-actions[bot], not the auth it uses to post the comment.
185-
bot_id: ${{ steps.auth.outputs.user-id }}
186-
bot_name: ${{ steps.auth.outputs.user-name }}
187-
allowed_bots: 'github-actions[bot],renovate[bot],claude[bot],automation-nsheaps[bot]'
188-
github_token: ${{ steps.auth.outputs.token }}
189-
settings: ${{ env.INPUT_SETTINGS_JSON }}
190-
additional_permissions: |
191-
actions: read
192-
# Prompt is loaded from external template file via interpolate-prompt action
193-
prompt: ${{ steps.prompt.outputs.prompt }}
194-
# Prefer using settings.json, but some are needed to be set here for the action
195-
# to automatically enable the MCP servers. Placing anything with `mcp__github__` prefix will enable the github mcp server.
196-
# See https://github.com/anthropics/claude-code-action/issues/723#issuecomment-3716307305
197-
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
198-
# or https://docs.claude.com/en/docs/claude-code/cli-reference for available options
199-
# Note: We intentionally do NOT include 'github_comment' (used for Claude's persistent "working on it" comment)
200-
# or 'github_inline_comment' (used for inline comments outside of reviews) in the allowed tools / claude_args,
201-
# to prevent Claude from posting persistent status comments or inline comments outside of proper reviews.
202-
claude_args: >-
203-
--allowedTools
204-
mcp__github__github_support_docs_search,
205-
mcp__github__get_pull_request,
206-
mcp__github__get_pull_request_diff,
207-
mcp__github__get_pull_request_files,
208-
mcp__github__get_pull_request_review_comments,
209-
mcp__github__get_pull_request_reviews,
210-
mcp__github__get_pull_request_status,
211-
mcp__github__create_pending_pull_request_review,
212-
mcp__github__add_comment_to_pending_review,
213-
mcp__github__submit_pending_pull_request_review

0 commit comments

Comments
 (0)