From 2d009d961be2d28680d3a30069561b585b4a2672 Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Sun, 2 Aug 2026 18:19:07 -0500 Subject: [PATCH] claude-code-review.yml: stop checking out fork PR head, restore base-branch checkout anthropics/claude-code-action's internal setupBranch() logic fetches a fork PR's branch itself via `git fetch origin pull//head:`, which requires `origin` to be the base repo (only the base repo carries refs/pull//head). Checking out the fork directly first, as this workflow's "Check out PR head" step did, points origin at the fork instead, which has no such ref -- breaking that fetch with "couldn't find remote ref pull//head" and silently failing the review on every fork PR (confirmed on Postgres-Extensions/cat_tools#54, reproduced identically on rerun). This exact failure mode was already fixed once, in c38cf2a ("never check out fork PR head..."), which switched to a plain base-branch checkout and let the action fetch the PR itself -- the safe, intended pattern per the action's own examples. db6ae56 ("add missing --comment flag") bundled in an accidental revert of that fix alongside its actual fix, reintroducing the fork checkout and the allow-unsafe-pr-checkout escape hatch. This restores the base-branch checkout on top of everything db6ae56 and later commits actually intended to change. Per .github/workflows/CLAUDE.md, pull_request_target always runs the workflow file from master, so this PR's own claude-review check cannot prove the fix -- verification happens on the next PR after this merges. --- .github/workflows/claude-code-review.yml | 36 ++++++++++++------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index d2292f3..1e2302d 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -9,8 +9,13 @@ name: Claude Code Review # write-capable token. The job is gated to PRs from the trusted `jnasbyupgrade` # fork only — an arbitrary external fork can never trigger this secret-bearing # job. The workflow file always comes from the base branch (master), so a PR -# cannot modify the reviewer that runs on it. We check out the PR head only for -# read context (persist-credentials: false) and never build or execute PR code. +# cannot modify the reviewer that runs on it. We never check out the fork's PR +# head ourselves here: anthropics/claude-code-action's own internal checkout +# logic (setupBranch() in src/github/operations/branch.ts) already fetches the +# PR branch via `git fetch origin pull//head`, which requires `origin` to be +# the BASE repo -- checking out the fork directly instead (as a prior version +# of this file did) points `origin` at the fork, which has no such ref, and +# breaks that fetch with "couldn't find remote ref pull//head". on: pull_request_target: types: [opened, synchronize, reopened, ready_for_review] @@ -24,13 +29,11 @@ jobs: # Trusted fork only, and skip drafts (don't spend API/CI on unfinished PRs). # To add more trusted owners, extend the head-owner check. # - # SECURITY-CRITICAL: this owner check is the ONLY thing that makes - # `allow-unsafe-pr-checkout: true` below acceptable. Without it, this - # pull_request_target job would check out and let Claude act on - # arbitrary fork code while holding base-repo secrets/token — a "pwn - # request". Do not remove or loosen this condition (e.g. drop the - # owner check, or allow non-owner forks) without re-evaluating the - # fork-checkout step's safety. + # SECURITY-CRITICAL: this owner check is what makes it safe to run this + # pull_request_target job -- which holds base-repo secrets/token -- on + # every fork PR unattended. Do not remove or loosen this condition (e.g. + # drop the owner check, or allow non-owner forks) without re-evaluating + # whether this job should keep running on arbitrary forks. if: >- github.event.pull_request.draft == false && github.event.pull_request.head.repo.owner.login == 'jnasbyupgrade' @@ -89,22 +92,19 @@ jobs: echo "decision=$decision" >> "$GITHUB_OUTPUT" echo "gate decision: $decision" - - name: Check out PR head (read-only context) + - name: Check out base branch if: steps.gate.outputs.decision == 'run' # Intentionally tracks the major-version tag (not a pinned SHA) so # upstream fixes are picked up automatically. + # + # No `repository:`/`ref:` here on purpose — this checks out the base + # branch (master), never the fork's PR head. See the SECURITY note + # above; anthropics/claude-code-action fetches the actual PR head + # itself afterward via the base repo's `refs/pull//head` ref. uses: actions/checkout@v6 with: - repository: ${{ github.event.pull_request.head.repo.full_name }} - ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 1 persist-credentials: false - # Unsafe by default (actions/checkout refuses fork-PR checkouts - # under pull_request_target/workflow_run). Only OK here because - # the job is gated to the project owner's forks — see the `if:` - # on the `claude-review` job above; that check is what makes - # this safe. - allow-unsafe-pr-checkout: true - name: Run Claude Code Review if: steps.gate.outputs.decision == 'run'