Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions .github/workflows/claude-code-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
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_target:
types: [opened, synchronize, reopened, ready_for_review]

concurrency:
group: claude-review-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
claude-review:
# 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)
# 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 }}
fetch-depth: 1
persist-credentials: false

- name: Run Claude Code Review
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
# marketplace repo's default branch (upstream anthropics/claude-code).
plugin_marketplaces: 'https://github.com/anthropics/claude-code.git'
plugins: 'code-review@claude-code-plugins'
Comment thread
jnasbyupgrade marked this conversation as resolved.
prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}'
46 changes: 46 additions & 0 deletions .github/workflows/claude.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Claude Code

on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]
Comment thread
jnasbyupgrade marked this conversation as resolved.

# No concurrency limit: @claude mentions are independent, read-only requests;
# serializing would only delay responses and cancelling would drop them.
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
timeout-minutes: 30
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
actions: read # Required for Claude to read CI results on PRs
Comment thread
jnasbyupgrade marked this conversation as resolved.
steps:
- name: Checkout repository
# 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@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# Allows Claude to read CI results on PRs
additional_permissions: |
actions: read
23 changes: 23 additions & 0 deletions lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,26 @@ debug() {
echo "DEBUG[$level]: $message" >&2
fi
}

# Remove pgxntool's own dev-only directories from a consuming project.
#
# `git subtree` copies the ENTIRE pgxntool tree into the consumer, including
# dev-only dirs like .github/ (pgxntool's CI) and .claude/. Those are
# export-ignored from `make dist` and don't belong in a project that merely
# embeds pgxntool. (GitHub only runs workflows at the repo root, so a consumer's
# pgxntool/.github never executes anyway — but it's still clutter.) git subtree
# doesn't honor export-ignore, so we prune them here after a sync.
#
# Must be run from the project root (the dir containing pgxntool/). Safe to call
# repeatedly; a no-op once the dirs are gone.
prune_pgxntool_dev_dirs() {
local d
for d in .github .claude; do
[ -e "pgxntool/$d" ] || continue
echo " pgxntool/$d: pruning (pgxntool dev-only, not for embedding projects)"
# Stage the removal if tracked; rm -rf guarantees it's gone even if not.
# || : keeps this best-effort under `set -e` (rm -rf is the real cleanup).
git rm -rq --ignore-unmatch "pgxntool/$d" >/dev/null 2>&1 || :
rm -rf "pgxntool/$d"
done
}
4 changes: 4 additions & 0 deletions update-setup-files.sh
Original file line number Diff line number Diff line change
Expand Up @@ -172,5 +172,9 @@ for entry in "${SETUP_SYMLINKS[@]}"; do
process_symlink "$dest" "$target"
done

# Prune pgxntool's own dev-only dirs (.github/, .claude/) that the subtree pull
# re-introduces but that don't belong in a consuming project (see lib.sh).
prune_pgxntool_dev_dirs

echo
echo "Done. Review changes with 'git diff' and commit when ready."
Loading