11name : Claude AI Review with inline comments
22
3+ # Instead of running the ai-reviewer GitHub Action inline, this workflow acts as
4+ # a thin, VPN-side relay to the Serge GitHub App hosted at
5+ # https://serge.huggingface.tech/. The App's /webhook endpoint sits behind a VPN
6+ # that GitHub's own webhook delivery cannot reach, so a runner inside the VPN
7+ # (group: aws-general-8-plus) re-delivers the triggering comment event to the App.
8+ #
9+ # The relay reproduces a genuine GitHub App webhook delivery:
10+ # - body: the original event payload with `installation.id` injected (the App
11+ # needs it to mint an installation token; Actions payloads omit it)
12+ # - X-Hub-Signature-256: HMAC-SHA256 of that exact body using the App's
13+ # webhook secret (verified at webapp.py:_verify_webhook_signature)
14+ # - X-GitHub-Event: the original event name (issue_comment / pull_request_review_comment)
15+ #
16+ # All reviewing, diff fetching and comment posting happens server-side under the
17+ # App identity, so this job needs no checkout and no write permissions.
18+
319on :
420 issue_comment :
521 types : [created]
824
925permissions :
1026 contents : read
11- pull-requests : write
12- issues : read
1327
1428jobs :
15- claude-ai-review :
29+ forward-to-serge-app :
1630 if : |
1731 (
1832 github.event_name == 'issue_comment' &&
@@ -32,35 +46,53 @@ jobs:
3246 concurrency :
3347 group : claude-ai-review-${{ github.event.issue.number || github.event.pull_request.number }}
3448 cancel-in-progress : false
35- runs-on : ubuntu-latest
49+ # Must run inside the VPN so https://serge.huggingface.tech/ is reachable.
50+ runs-on :
51+ group : aws-general-8-plus
3652 steps :
37- - name : Resolve PR number
38- id : pr
53+ - name : Relay event to the Serge GitHub App
54+ env :
55+ WEBHOOK_URL : https://serge.huggingface.tech/webhook
56+ # App webhook secret — must match the App's GITHUB_WEBHOOK_SECRET.
57+ WEBHOOK_SECRET : ${{ secrets.SERGE_WEBHOOK_SECRET }}
58+ # Installation id of the Serge App on this repo. Not sensitive, but the
59+ # App requires it in the payload to obtain an installation token.
60+ INSTALLATION_ID : ${{ secrets.SERGE_INSTALLATION_ID }}
61+ EVENT_NAME : ${{ github.event_name }}
62+ DELIVERY_ID : ${{ github.run_id }}-${{ github.run_attempt }}
3963 run : |
40- NUM="${{ github.event.issue.number || github.event.pull_request.number }}"
41- echo "number=${NUM}" >> "$GITHUB_OUTPUT"
64+ set -euo pipefail
65+
66+ if [ -z "${WEBHOOK_SECRET}" ]; then
67+ echo "::error::SERGE_WEBHOOK_SECRET secret is not set" >&2
68+ exit 1
69+ fi
70+ if [ -z "${INSTALLATION_ID}" ]; then
71+ echo "::error::SERGE_INSTALLATION_ID secret is not set" >&2
72+ exit 1
73+ fi
74+
75+ # Inject installation.id into the original event payload, compact form.
76+ # The signed bytes and the POSTed bytes must be byte-identical, so we
77+ # write the body to a file and reuse it for both the HMAC and the POST.
78+ jq -c --argjson iid "${INSTALLATION_ID}" \
79+ '. + {installation: {id: $iid}}' \
80+ "${GITHUB_EVENT_PATH}" > payload.json
4281
43- - name : Check out PR head (shallow)
44- uses : actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
45- with :
46- ref : refs/pull/${{ steps.pr.outputs.number }}/head
47- fetch-depth : 1
82+ SIG="sha256=$(openssl dgst -sha256 -hmac "${WEBHOOK_SECRET}" payload.json | awk '{print $NF}')"
4883
49- - name : Strip fork-supplied reviewer/agent config
50- # ai-reviewer fetches its config (.ai/review-rules.md, .ai/review-tools.json,
51- # .ai/context-script) from the base repo's default branch via the GitHub
52- # Contents API, so wiping the fork's local copies does not affect rule
53- # loading. The wipe matters because the action also exposes read-only
54- # browse tools (read_file/list_dir/grep) rooted at the PR-head checkout —
55- # without this step a fork could ship its own .ai/review-tools.json or
56- # .ai/context-script and surface them to the LLM. .claude/ + CLAUDE.md
57- # are wiped for parity with the hardening in claude_review.yml.
58- run : rm -rf .ai/ .claude/ CLAUDE.md
84+ HTTP_CODE=$(curl --silent --show-error --fail-with-body \
85+ --output response.txt --write-out '%{http_code}' \
86+ --request POST "${WEBHOOK_URL}" \
87+ --header "Content-Type: application/json" \
88+ --header "X-GitHub-Event: ${EVENT_NAME}" \
89+ --header "X-GitHub-Delivery: ${DELIVERY_ID}" \
90+ --header "X-Hub-Signature-256: ${SIG}" \
91+ --data-binary @payload.json) || {
92+ echo "::error::Failed to deliver event to Serge App (HTTP ${HTTP_CODE:-000})" >&2
93+ cat response.txt >&2 || true
94+ exit 1
95+ }
5996
60- - uses : huggingface/ai-reviewer@main
61- with :
62- llm_api_key : ${{ secrets.ANTHROPIC_API_KEY }}
63- llm_api_base : https://api.anthropic.com
64- llm_model : claude-opus-4-6
65- llm_stream : ' true'
66- mention_trigger : ' @askserge'
97+ echo "Serge App responded with HTTP ${HTTP_CODE}"
98+ cat response.txt
0 commit comments