Skip to content

ci(claude-review): run reviews on fork PRs via pull_request_target#5104

Merged
AVGVSTVS96 merged 2 commits into
mainfrom
ci/claude-review-fork-prs
Jul 23, 2026
Merged

ci(claude-review): run reviews on fork PRs via pull_request_target#5104
AVGVSTVS96 merged 2 commits into
mainfrom
ci/claude-review-fork-prs

Conversation

@AVGVSTVS96

@AVGVSTVS96 AVGVSTVS96 commented Jul 22, 2026

Copy link
Copy Markdown
Member

Problem

Claude review never runs on external contributors' PRs such as #5085. Fork pull_request runs do not receive repository secrets or write tokens, while the Claude action's GitHub App exchange does not support pull_request_target.

Solution

Keep the existing same-repository path unchanged (pull_request, Claude App token, claude[bot]) and add a separate automatic fork path (pull_request_target, short-lived workflow token, github-actions[bot]). Event and repository-origin guards make the two paths mutually exclusive.

Fork security boundary

The fork job follows GitHub's pull_request_target hardening guidance and the Claude action's non-write-user guidance:

  • Only the trusted base branch is checked out. Contributor code is never downloaded to the runner, and checkout does not persist credentials.
  • The fork job has only contents: read and pull-requests: write; it receives no OIDC or Actions-log permission.
  • track_progress: false keeps the action in agent mode. Shell, filesystem, web, editing, and subagent tools are explicitly denied with dontAsk mode.
  • Claude can inspect the PR only through an exact allowlist of read-only GitHub MCP tools. Its only direct write is an inline comment on the current PR.
  • Claude cannot create or select top-level comments. A trusted post-step publishes the final result and updates only a workflow-specific marker comment.
  • allowed_non_write_users: '*' enables automatic external reviews and activates the action's subprocess environment scrubbing and token-free Git credential helper.

The accepted residual risks are external users consuming Claude quota and prompt injection influencing review content on their own PR. The workflow does not intentionally expose a path to secrets, command execution, code changes, pushes, or broader repository writes.

Verification

  • actionlint and its shellcheck integration pass (apart from the repository's existing custom Blacksmith runner-label notice when not configured locally).
  • Event routing was checked for same-repository and fork PRs across both triggers.
  • Every allowlisted MCP tool exists in the exact GitHub MCP release used by the pinned Claude action.
  • The pinned action source was verified for agent-mode selection, non-write-user permission handling, token scrubbing, and credential-helper behavior.
  • git diff --check passes. This workflow-only change does not need a changeset.

Once merged, the next push or reopen on an external PR will trigger the automatic hardened review path.

@AVGVSTVS96
AVGVSTVS96 requested review from a team and Copilot July 22, 2026 10:04
@changeset-bot

changeset-bot Bot commented Jul 22, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 0aa32eb

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@rupic-app rupic-app Bot added stage/needs-review No reviews yet; waiting for a maintainer R1 Actionable, normal priority type/chore Tooling, deps, CI, formatting labels Jul 22, 2026
@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Reviews (1): Last reviewed commit: "ci(claude-review): run reviews on fork P..." | Re-trigger Greptile

Comment thread .github/workflows/claude-code-review.yml Outdated
@rupic-app rupic-app Bot added stage/awaiting-reviewer Has reviews but not approved; reviewer follow-up needed and removed stage/needs-review No reviews yet; waiting for a maintainer labels Jul 22, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a safe, label-gated pull_request_target path so Claude Code Review can run on fork PRs (where pull_request runs don’t receive secrets/OIDC), while keeping the existing same-repo pull_request behavior unchanged.

Changes:

  • Introduces a pull_request_target (label) trigger for fork PR reviews gated by the new fable-review label, and consumes the label after triggering.
  • Hardens fork reviews by keeping the trusted base branch at repo root and exposing the PR head in a separate ./pr-head directory for read-only inspection.
  • Adjusts post-steps to resolve the review bot identity depending on event type (fork vs same-repo).

Comment thread .github/workflows/claude-code-review.yml Outdated

@rupic-app rupic-app Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This adds a label-gated pull_request_target path so Claude review runs on fork PRs, and the security model is sound: the trusted base branch stays at the workspace root, the contributor head is checked out to ./pr-head pinned to the labeled SHA and added via --add-dir, and the prompt tells the reviewer to treat that tree as untrusted input. I verified the label-consumption step, the pinned-head checkout, the per-event bot-identity resolution in the post-steps, and the empty-github_token preservation of the OIDC path from the diff. One guard change should land before merge.

  • Please wrap each disjunct of the job if in parentheses in .github/workflows/claude-code-review.yml. The guard mixes && and || and relies on operator precedence for the gate that keeps fork authors from triggering a secret-bearing run, while the sibling changeset-semver-check.yaml:214-216 parenthesizes exactly this kind of mix and the fork path's runtime behavior cannot be confirmed from the diff alone.
  • While editing that guard, please decide whether the fork disjunct should keep the draft == false and sender.type != 'Bot' checks the same-repo path keeps, and say so in the if comment; as written, a draft or bot-labeled fork PR is reviewed where a same-repo one is not.

Two smaller notes: the description does not mention that the job permission was widened from issues: read to issues: write (.github/workflows/claude-code-review.yml:85), which also affects same-repo runs, so please note why that is needed; and id-token: write is granted on the fork path although the OIDC exchange is skipped there (github_token is passed directly), so it is unused on that path.

Review threads

  • confirmed: issue-comment @changeset-bot[bot] 2026-07-22T10:04:14Z by @changeset-bot[bot]: No changeset is present, which is correct for this CI-only change touching no published package; the bot's own message says you are good to go if no version bump is intended. Not blocking, and the live Semver Check passed.

Review by Rupic. Verified against the diff; runtime behavior was not executed.

Fork PRs were skipped entirely: pull_request runs from forks get no
secrets and the OIDC exchange rejects them. Run fork PRs on
pull_request_target instead, handing GITHUB_TOKEN to the action
directly; those reviews post as github-actions[bot]. The trusted base
branch stays at the workspace root so Claude Code never loads CLAUDE.md
or .claude/ settings from contributor code, with the PR head exposed
read-only at ./pr-head via --add-dir.
Copilot AI review requested due to automatic review settings July 22, 2026 10:18
@AVGVSTVS96
AVGVSTVS96 force-pushed the ci/claude-review-fork-prs branch from a96abc2 to e846cb6 Compare July 22, 2026 10:18
@AVGVSTVS96 AVGVSTVS96 changed the title ci(claude-review): run reviews on fork PRs via label-gated pull_request_target ci(claude-review): run reviews on fork PRs via pull_request_target Jul 22, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

.github/workflows/claude-code-review.yml:107

  • For fork PRs, checking out by ref: ${{ github.event.pull_request.head.sha }} without also setting repository: will try to fetch that commit from the base repo, which usually doesn't contain the fork's SHA. Set repository to the PR head repo so the pinned SHA checkout works reliably on pull_request_target.
        with:
          ref: ${{ github.event.pull_request.head.sha }}
          path: pr-head
          fetch-depth: 1

Comment thread .github/workflows/claude-code-review.yml Outdated
Comment thread .github/workflows/claude-code-review.yml Outdated
@AVGVSTVS96
AVGVSTVS96 marked this pull request as draft July 22, 2026 10:51
Copilot AI review requested due to automatic review settings July 22, 2026 11:08
@rupic-app rupic-app Bot added stage/draft Author still working; not ready for review R3 Draft; not ready for review and removed stage/awaiting-reviewer Has reviews but not approved; reviewer follow-up needed R1 Actionable, normal priority labels Jul 22, 2026
@AVGVSTVS96
AVGVSTVS96 marked this pull request as ready for review July 22, 2026 11:09
@rupic-app rupic-app Bot added stage/awaiting-reviewer Has reviews but not approved; reviewer follow-up needed and removed stage/draft Author still working; not ready for review labels Jul 22, 2026
@rupic-app rupic-app Bot added R1 Actionable, normal priority and removed R3 Draft; not ready for review labels Jul 22, 2026
@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Reviews (2): Last reviewed commit: "fix(ci): harden automatic fork reviews" | Re-trigger Greptile

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment thread .github/workflows/claude-code-review.yml

@rupic-app rupic-app Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR adds a hardened pull_request_target job so Claude review runs on fork PRs, and I recommend merging it. The two jobs are mutually exclusive via event-name and repository-origin guards: claude-review requires github.event_name == 'pull_request' and head.repo.full_name == github.repository, while claude-fork-review requires github.event_name == 'pull_request_target' and a fork head repo. Because adding pull_request_target makes both events fire for same-repo PRs, the new event_name check on the existing job is necessary to prevent a double run, and the check-run state for this PR confirms it: claude-fork-review is skipped here. The same-repository path's steps are otherwise unchanged; only its if guard and the shared REVIEW_PROMPT prose moved, and the prose change drops a dead reference to mcp__github_ci__* tools that were never in the same-repo allowlist. The fork job follows GitHub's pull_request_target hardening guidance: it checks out only the trusted base branch with persist-credentials: false, declares only contents: read and pull-requests: write, passes the built-in github.token, and denies Bash, Read, Write, Glob, Grep, WebFetch, WebSearch, and Agent under dontAsk, leaving read-only GitHub MCP tools plus a single inline-comment write. The trusted post-step publishes the result and updates a workflow-specific marker comment, which also retires the earlier shared-bot-identity concern; every prior review comment (label-gating, ./pr-head, issues: write, id-token: write, guard precedence, secrets.GITHUB_TOKEN) is resolved by this rework. The actionlint, allowlisted-MCP-tool, and action-internal (env scrubbing, credential helper) claims are unverifiable from the diff, as is the fork publish step's runtime success; the same-repo job already PATCHes a PR comment with issues: read + pull-requests: write and no issues: write, and sticky-PR-comment actions post and update PR comments with only pull-requests: write, so it should work and fails loudly if not. Please consider adding issues: read to the fork job's permissions to match the same-repository claude-review job, which declares it for the identical gh api .../issues/{n}/comments read the publish step performs; it is harmless and removes any doubt about the marker-comment lookup.

Review threads

  • fixed: review-threads.md .github/workflows/claude-code-review.yml:? (greptile-apps) by @greptile-apps[bot]: The publish step now filters by user.login == github-actions[bot] AND body contains the marker, then PATCHes that specific comment, so it no longer selects the latest generic github-actions[bot] comment.
  • fixed: review-threads.md .github/workflows/claude-code-review.yml:? (copilot: head.sha checkout) by @copilot-pull-request-reviewer[bot]: The rework no longer checks out the head SHA; it checks out the trusted base branch with no ref, so the fork-SHA-not-in-base-repo problem is moot.
  • fixed: review-threads.md .github/workflows/claude-code-review.yml:? (copilot: empty ref) by @copilot-pull-request-reviewer[bot]: The fork checkout omits ref entirely (only fetch-depth and persist-credentials), so there is no empty ref to misinterpret.
  • fixed: review-threads.md .github/workflows/claude-code-review.yml:? (copilot: secrets.GITHUB_TOKEN) by @copilot-pull-request-reviewer[bot]: The fork job uses github_token: ${{ github.token }} and GH_TOKEN: ${{ github.token }}, the built-in token, not secrets.GITHUB_TOKEN.
  • stale: prior-reviews.md rupic-app[bot] (parenthesize disjuncts) by @rupic-app[bot]: The current if guard uses only && (no ||), so the operator-precedence concern is moot; the rework removed the disjunctive mix it described.
  • fixed: prior-reviews.md rupic-app[bot] (draft/bot checks on fork path) by @rupic-app[bot]: The fork guard keeps github.event.pull_request.draft == false and github.event.sender.type != 'Bot', matching the same-repo path.
  • stale: prior-reviews.md rupic-app[bot] (issues: write widening) by @rupic-app[bot]: The rework removed the issues: write widening; the fork job declares no issues permission at all.
  • fixed: prior-reviews.md rupic-app[bot] (id-token: write unused on fork path) by @rupic-app[bot]: The fork job no longer grants id-token: write; it passes github.token directly, so OIDC is not requested.
  • confirmed: issue-comments.md changeset-bot 2026-07-22T10:04:14Z by @changeset-bot[bot]: Correct for a CI-only change touching no published package; the bot's own message says you are good to go if no version bump is intended. Not blocking.

Review by Rupic. Verified against the diff; runtime behavior was not executed.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 1 file

Re-trigger cubic

@rupic-app rupic-app Bot added stage/ready Approved with passing CI; ready to merge R4 Ready to merge and removed stage/awaiting-reviewer Has reviews but not approved; reviewer follow-up needed R1 Actionable, normal priority labels Jul 23, 2026
@AVGVSTVS96
AVGVSTVS96 merged commit 2d4e8e8 into main Jul 23, 2026
25 of 30 checks passed
@AVGVSTVS96
AVGVSTVS96 deleted the ci/claude-review-fork-prs branch July 23, 2026 07:21
@rupic-app rupic-app Bot added stage/merged Merged into the default branch and removed stage/ready Approved with passing CI; ready to merge R4 Ready to merge labels Jul 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stage/merged Merged into the default branch type/chore Tooling, deps, CI, formatting

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants