From 31673acdea3dadf4a5b77a09445f5565ba24bf84 Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Tue, 4 Aug 2026 16:28:34 -0500 Subject: [PATCH 1/2] CI: bump actions/checkout to v7 (v4 targets Node.js 20, now deprecated) GitHub Actions runners have started warning that Node.js 20 is deprecated and actions pinned to it are being forced onto Node.js 24 at runtime. actions/checkout@v4 is one of those; v7 is the current latest major and already targets Node 24 natively. anthropics/claude-code-action is already on its latest major (v1, no v2 exists yet) so it's untouched. v7 also added a default-deny guard: it refuses to check out a fork's PR head when the trigger is pull_request_target or workflow_run (the classic "pwn request" pattern), unless allow-unsafe-pr-checkout: true is set. claude-code-review.yml's "Check out PR head" step does exactly that under pull_request_target, so it needed the explicit opt-in to keep working - already safe here per the guards documented at the top of that workflow (gated to jnasbyupgrade's own PRs, persist-credentials: false, checked-out code only ever read for review context, never built or executed). The other two checkout call sites (ci.yml x3, claude.yml) just check out this repo's own ref and needed no such change. No local way to confirm the deprecation warning itself is gone - that only shows up in real Actions run logs, not anything checkable from a clone. Needs a real CI run on this PR to confirm. --- .github/workflows/ci.yml | 6 +++--- .github/workflows/claude-code-review.yml | 10 +++++++++- .github/workflows/claude.yml | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5111af6..b5e8f3d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out the repo - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: make lint # Deliberately not pre-initializing the .vendor/linter submodule via # `submodules:` above -- letting `make lint` self-init it (lint.mk) @@ -28,7 +28,7 @@ jobs: - name: Start PostgreSQL ${{ matrix.pg }} run: pg-start ${{ matrix.pg }} - name: Check out the repo - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Test on PostgreSQL ${{ matrix.pg }} run: pg-build-test @@ -64,7 +64,7 @@ jobs: - name: Start PostgreSQL ${{ matrix.pg }} run: pg-start ${{ matrix.pg }} - name: Check out the repo - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Install rsync run: apt-get install -y rsync - name: Snapshot filesystem extension control files (pre-pgtap baseline) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 8efa338..dee5528 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -85,12 +85,20 @@ jobs: if: steps.gate.outputs.decision == 'run' # Intentionally tracks the major-version tag (not a pinned SHA) so # upstream fixes are picked up automatically. - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: repository: ${{ github.event.pull_request.head.repo.full_name }} ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 1 persist-credentials: false + # v7 refuses by default to check out a fork's PR head under + # pull_request_target (the classic "pwn request" footgun), since + # that trigger runs with this repo's secrets/token. Safe to opt in + # here specifically because of the guards already in place above: + # gated to jnasbyupgrade's own PRs only, persist-credentials is + # false, and the checked-out code is only ever read for review + # context -- never built or executed. + allow-unsafe-pr-checkout: true - name: Run Claude Code Review if: steps.gate.outputs.decision == 'run' diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 7d1656c..dc559d1 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -36,7 +36,7 @@ jobs: - name: Checkout repository # Intentionally tracks the major-version tag (not a pinned SHA) so # upstream fixes are picked up automatically. - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: fetch-depth: 1 persist-credentials: false From 3fde15d72d83b856c9179650efa8885530f2d9a0 Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Wed, 5 Aug 2026 17:20:30 -0500 Subject: [PATCH 2/2] Fix claude-code-review.yml: drop unsafe fork checkout, add missing inline-comment tool and cache permission The prior fix for actions/checkout@v7's fork-checkout refusal added allow-unsafe-pr-checkout: true to keep checking out the PR head into the workspace. That's the wrong fix for a step whose only purpose is feeding anthropics/claude-code-action, which already fetches and reads a PR's content itself internally via refs/pull//head on the base repo -- per the action's own security docs, checking out an untrusted PR ref first is the anti-pattern to avoid. Switch to a plain, unmodified checkout of the base ref instead. While in this file: add the claude_args --allowedTools flag needed for the code-review plugin's inline PR comments to actually post (agent-mode prompt: runs don't register MCP tools from the plugin's own frontmatter, so without this the inline_comment server never starts and reviews silently fall back to one consolidated comment -- confirmed happening in a recent real run's log, "No buffered inline comments"). Also add actions: write to the job's permissions, since there's no narrower cache-write scope and its absence was silently causing "Cache reservation failed" warnings in that same run's log. --- .github/workflows/claude-code-review.yml | 44 +++++++++++++++--------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index dee5528..7837b06 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -16,8 +16,10 @@ name: Claude Code Review # still can't trigger this secret-bearing job unless it's actually # jnasbyupgrade's own fork). 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. +# never check out the PR head ourselves -- claude-code-action fetches and +# reads the PR's actual content internally, via refs/pull//head on this +# base repo, so the fork's code is only ever read, never checked out into +# this job's own workspace or built/executed. on: pull_request_target: types: [opened, synchronize, reopened, ready_for_review] @@ -39,6 +41,11 @@ jobs: contents: read pull-requests: write # post the review comments checks: read # read sibling check-runs for the cost gate + # No narrower scope exists for cache-write alone; without this the + # action's own cache save step fails with a silent warning ("Cache + # reservation failed: cache write denied: token has no writable + # scopes") and the job still reports success, just slower/uncached. + actions: write steps: # COST GATE: the paid Claude review is the last thing to run. Wait for the # PR head's OTHER check-runs to finish and only proceed if they are clean. @@ -81,24 +88,21 @@ jobs: echo "decision=$decision" >> "$GITHUB_OUTPUT" echo "gate decision: $decision" - - name: Check out PR head (read-only context) + - name: Check out repo (base ref -- claude-code-action fetches the PR itself) if: steps.gate.outputs.decision == 'run' # Intentionally tracks the major-version tag (not a pinned SHA) so # upstream fixes are picked up automatically. + # + # Deliberately NOT overriding repository:/ref: to the PR head, and NOT + # setting allow-unsafe-pr-checkout: true. claude-code-action fetches + # and reads the PR's actual content itself, internally and safely, via + # refs/pull//head on THIS (base) repo -- a ref GitHub maintains for + # any PR, fork or not, specifically so tooling never needs direct + # access to the fork's own remote. Per the action's own docs + # (docs/security.md), checking out an untrusted PR ref into the + # workspace before invoking it is the anti-pattern to avoid, so this + # step just checks out the base branch, plain default behavior. uses: actions/checkout@v7 - with: - repository: ${{ github.event.pull_request.head.repo.full_name }} - ref: ${{ github.event.pull_request.head.sha }} - fetch-depth: 1 - persist-credentials: false - # v7 refuses by default to check out a fork's PR head under - # pull_request_target (the classic "pwn request" footgun), since - # that trigger runs with this repo's secrets/token. Safe to opt in - # here specifically because of the guards already in place above: - # gated to jnasbyupgrade's own PRs only, persist-credentials is - # false, and the checked-out code is only ever read for review - # context -- never built or executed. - allow-unsafe-pr-checkout: true - name: Run Claude Code Review if: steps.gate.outputs.decision == 'run' @@ -116,6 +120,14 @@ jobs: # tracking PR comment with a live checklist that updates as Claude # works, so a slow run is visible instead of silent. track_progress: true + # A bare `prompt:` (no @claude mention) runs in "agent mode", which + # decides which MCP servers to start by scanning --allowedTools + # inside claude_args -- it does NOT consult the code-review + # plugin's own allowed-tools frontmatter. Without this, the + # github_inline_comment MCP server never starts and the plugin + # silently falls back to one consolidated PR comment instead of + # real per-line inline comments (no error or warning either way). + claude_args: '--allowedTools mcp__github_inline_comment__create_inline_comment' # NOTE: plugin_marketplaces can't be pinned — it tracks the # marketplace repo's default branch (upstream anthropics/claude-code). plugin_marketplaces: 'https://github.com/anthropics/claude-code.git'