From 80259bee99c957166b9f843b75377181e3a71dbb Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Mon, 13 Jul 2026 17:14:09 -0500 Subject: [PATCH 1/6] Add Claude Code GitHub Actions workflows --- .github/workflows/claude-code-review.yml | 44 +++++++++++++++++++++ .github/workflows/claude.yml | 50 ++++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 .github/workflows/claude-code-review.yml create mode 100644 .github/workflows/claude.yml diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml new file mode 100644 index 0000000..b5e8cfd --- /dev/null +++ b/.github/workflows/claude-code-review.yml @@ -0,0 +1,44 @@ +name: Claude Code Review + +on: + pull_request: + types: [opened, synchronize, ready_for_review, reopened] + # Optional: Only run on specific file changes + # paths: + # - "src/**/*.ts" + # - "src/**/*.tsx" + # - "src/**/*.js" + # - "src/**/*.jsx" + +jobs: + claude-review: + # Optional: Filter by PR author + # if: | + # github.event.pull_request.user.login == 'external-contributor' || + # github.event.pull_request.user.login == 'new-developer' || + # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' + + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + issues: read + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run Claude Code Review + id: claude-review + uses: anthropics/claude-code-action@v1 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + plugin_marketplaces: 'https://github.com/anthropics/claude-code.git' + plugins: 'code-review@claude-code-plugins' + prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}' + # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md + # or https://code.claude.com/docs/en/cli-reference for available options + diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml new file mode 100644 index 0000000..6b15fac --- /dev/null +++ b/.github/workflows/claude.yml @@ -0,0 +1,50 @@ +name: Claude Code + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + issues: + types: [opened, assigned] + pull_request_review: + types: [submitted] + +jobs: + claude: + if: | + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || + (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + issues: read + id-token: write + actions: read # Required for Claude to read CI results on PRs + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run Claude Code + id: claude + uses: anthropics/claude-code-action@v1 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + + # This is an optional setting that allows Claude to read CI results on PRs + additional_permissions: | + actions: read + + # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it. + # prompt: 'Update the pull request description to include a summary of changes.' + + # Optional: Add claude_args to customize behavior and configuration + # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md + # or https://code.claude.com/docs/en/cli-reference for available options + # claude_args: '--allowed-tools Bash(gh pr *)' + From 17e34cb7ea1e408f8c36b18e8a1e1b515b90f064 Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Mon, 13 Jul 2026 17:30:45 -0500 Subject: [PATCH 2/6] ci: review fork PRs via pull_request_target (gated to jnasbyupgrade) `claude-code-review.yml` used `on: pull_request`, which gets no secrets on fork PRs, so CLAUDE_CODE_OAUTH_TOKEN was empty and review never ran for our fork-based PRs. Switch to `pull_request_target` (runs in the base repo with the secret available) and gate the job to PRs from the jnasbyupgrade fork only, so external forks can't trigger the secret-bearing job. Check out the PR head read-only (persist-credentials: false); no PR code is built or executed. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/claude-code-review.yml | 42 +++++++++++------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index b5e8cfd..951337c 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -1,44 +1,42 @@ name: Claude Code Review +# Runs on PRs INTO this repo. We use pull_request_target (not pull_request) so +# that PRs from a fork can access CLAUDE_CODE_OAUTH_TOKEN — GitHub withholds +# secrets from `pull_request` runs triggered by forks, which is why the plain +# `pull_request` version never worked for fork PRs. +# +# SECURITY: pull_request_target runs in the BASE repo with secrets and a +# 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. on: - pull_request: - types: [opened, synchronize, ready_for_review, reopened] - # Optional: Only run on specific file changes - # paths: - # - "src/**/*.ts" - # - "src/**/*.tsx" - # - "src/**/*.js" - # - "src/**/*.jsx" + pull_request_target: + types: [opened, synchronize, reopened, ready_for_review] jobs: claude-review: - # Optional: Filter by PR author - # if: | - # github.event.pull_request.user.login == 'external-contributor' || - # github.event.pull_request.user.login == 'new-developer' || - # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' - + # Only the trusted fork. To add more trusted owners, extend this condition. + if: github.event.pull_request.head.repo.owner.login == 'jnasbyupgrade' runs-on: ubuntu-latest permissions: contents: read - pull-requests: read - issues: read + pull-requests: write # post the review comments id-token: write - steps: - - name: Checkout repository + - name: Check out PR head (read-only context) uses: actions/checkout@v4 with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 1 + persist-credentials: false - name: Run Claude Code Review - id: claude-review uses: anthropics/claude-code-action@v1 with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} plugin_marketplaces: 'https://github.com/anthropics/claude-code.git' plugins: 'code-review@claude-code-plugins' prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}' - # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md - # or https://code.claude.com/docs/en/cli-reference for available options - From d11ef040177f6bf4657f3bd14446f4998081d1ef Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Mon, 13 Jul 2026 18:09:27 -0500 Subject: [PATCH 3/6] ci: harden claude workflows (CodeRabbit review on #41) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add concurrency groups (cancel stale review runs; serialize @claude without cancelling in-progress responses). - Add timeout-minutes: 30 to both jobs. - Skip draft PRs in the auto-review. - Pin actions/checkout and anthropics/claude-code-action to immutable SHAs. - persist-credentials: false on all checkouts. (plugin_marketplaces can't be pinned — it tracks the marketplace default branch.) Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/claude-code-review.yml | 18 ++++++++++++++---- .github/workflows/claude.yml | 24 +++++++++++------------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 951337c..b1f6cd5 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -15,18 +15,26 @@ on: pull_request_target: types: [opened, synchronize, reopened, ready_for_review] +concurrency: + group: claude-review-${{ github.event.pull_request.number }} + cancel-in-progress: true + jobs: claude-review: - # Only the trusted fork. To add more trusted owners, extend this condition. - if: github.event.pull_request.head.repo.owner.login == 'jnasbyupgrade' + # Trusted fork only, and skip drafts (don't spend API/CI on unfinished PRs). + # To add more trusted owners, extend the head-owner check. + if: >- + github.event.pull_request.draft == false && + github.event.pull_request.head.repo.owner.login == 'jnasbyupgrade' runs-on: ubuntu-latest + timeout-minutes: 30 permissions: contents: read pull-requests: write # post the review comments id-token: write steps: - name: Check out PR head (read-only context) - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: repository: ${{ github.event.pull_request.head.repo.full_name }} ref: ${{ github.event.pull_request.head.sha }} @@ -34,9 +42,11 @@ jobs: persist-credentials: false - name: Run Claude Code Review - uses: anthropics/claude-code-action@v1 + uses: anthropics/claude-code-action@e90deca47693f9457b72f2b53c17d7c445a87342 # v1 with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + # 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' plugins: 'code-review@claude-code-plugins' prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}' diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 6b15fac..b5638d2 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -10,6 +10,12 @@ on: pull_request_review: types: [submitted] +# Serialize per issue/PR. cancel-in-progress is false on purpose: cancelling +# would kill Claude mid-response if another comment lands while it's working. +concurrency: + group: claude-${{ github.event.issue.number || github.event.pull_request.number }} + cancel-in-progress: false + jobs: claude: if: | @@ -18,6 +24,7 @@ jobs: (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) runs-on: ubuntu-latest + timeout-minutes: 30 permissions: contents: read pull-requests: read @@ -26,25 +33,16 @@ jobs: actions: read # Required for Claude to read CI results on PRs steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: fetch-depth: 1 + persist-credentials: false - name: Run Claude Code id: claude - uses: anthropics/claude-code-action@v1 + uses: anthropics/claude-code-action@e90deca47693f9457b72f2b53c17d7c445a87342 # v1 with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - - # This is an optional setting that allows Claude to read CI results on PRs + # Allows Claude to read CI results on PRs additional_permissions: | actions: read - - # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it. - # prompt: 'Update the pull request description to include a summary of changes.' - - # Optional: Add claude_args to customize behavior and configuration - # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md - # or https://code.claude.com/docs/en/cli-reference for available options - # claude_args: '--allowed-tools Bash(gh pr *)' - From f55ba9a2d18b35e7ffae8452a43256a23e784e16 Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Mon, 13 Jul 2026 18:20:34 -0500 Subject: [PATCH 4/6] ci: track major-version action tags instead of pinned SHAs Revert the SHA pins on actions/checkout and anthropics/claude-code-action back to @v4/@v1. For these first-party actions we want upstream fixes to flow in automatically rather than freezing at a SHA. (CodeRabbit/zizmor will flag the unpinned refs; that's an accepted trade-off here.) Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/claude-code-review.yml | 6 ++++-- .github/workflows/claude.yml | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index b1f6cd5..377996a 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -34,7 +34,9 @@ jobs: id-token: write steps: - name: Check out PR head (read-only context) - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + # Intentionally tracks the major-version tag (not a pinned SHA) so + # upstream fixes are picked up automatically. + uses: actions/checkout@v4 with: repository: ${{ github.event.pull_request.head.repo.full_name }} ref: ${{ github.event.pull_request.head.sha }} @@ -42,7 +44,7 @@ jobs: persist-credentials: false - name: Run Claude Code Review - uses: anthropics/claude-code-action@e90deca47693f9457b72f2b53c17d7c445a87342 # v1 + uses: anthropics/claude-code-action@v1 with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} # NOTE: plugin_marketplaces can't be pinned — it tracks the diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index b5638d2..b316236 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -33,14 +33,16 @@ jobs: actions: read # Required for Claude to read CI results on PRs steps: - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + # Intentionally tracks the major-version tag (not a pinned SHA) so + # upstream fixes are picked up automatically. + uses: actions/checkout@v4 with: fetch-depth: 1 persist-credentials: false - name: Run Claude Code id: claude - uses: anthropics/claude-code-action@e90deca47693f9457b72f2b53c17d7c445a87342 # v1 + uses: anthropics/claude-code-action@v1 with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} # Allows Claude to read CI results on PRs From df620e92b00849cc262867cf438d73bc88a1bf0f Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Mon, 13 Jul 2026 18:27:57 -0500 Subject: [PATCH 5/6] ci: drop concurrency limit from the @claude workflow @claude mentions are independent, read-only requests; serializing only delays responses and cancelling would drop them. Concurrency stays on claude-code-review.yml (cancel stale review runs), where it's useful. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/claude.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index b316236..c85ec00 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -10,12 +10,8 @@ on: pull_request_review: types: [submitted] -# Serialize per issue/PR. cancel-in-progress is false on purpose: cancelling -# would kill Claude mid-response if another comment lands while it's working. -concurrency: - group: claude-${{ github.event.issue.number || github.event.pull_request.number }} - cancel-in-progress: false - +# No concurrency limit: @claude mentions are independent, read-only requests; +# serializing would only delay responses and cancelling would drop them. jobs: claude: if: | From 06ae23bdd0b0ac1886e4cd12d90f333888d89a3a Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Mon, 13 Jul 2026 18:34:52 -0500 Subject: [PATCH 6/6] test: cover pruning of pgxntool dev-only dirs on sync Add tests/updates for pgxntool commit ec1150d: - update-setup-files.sh prunes pgxntool/.github and .claude (dev-only dirs copied in by git subtree) from consuming projects Assert pgxntool/.github is present before the sync and gone after. Co-Authored-By: Claude Opus 4.8 (1M context) --- test/standard/update-setup-files.bats | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/standard/update-setup-files.bats b/test/standard/update-setup-files.bats index ff541f1..2c96894 100644 --- a/test/standard/update-setup-files.bats +++ b/test/standard/update-setup-files.bats @@ -257,4 +257,21 @@ setup() { assert_contains "$output" "Checking setup files for updates" } +# pgxntool's own dev-only dirs (.github/, .claude/) are copied in by git subtree +# but shouldn't live in a consuming project. update-setup-files.sh prunes them. +@test "prunes pgxntool's dev-only .github after sync" { + local old_commit + old_commit=$(git log -1 --format=%H -- pgxntool/) + + # Sanity: it's present before the sync (came in via subtree). + [ -e pgxntool/.github ] + + run pgxntool/update-setup-files.sh "$old_commit" + assert_success + assert_contains "$output" "pruning" + + # Pruned from the working tree, and the removal is staged. + [ ! -e pgxntool/.github ] +} + # vi: expandtab sw=2 ts=2