From 34118d0a37ee1ee78dff49a217c3da0b80e2ea6e Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Sat, 1 Aug 2026 17:12:04 -0500 Subject: [PATCH 1/4] claude-code-review.yml: add a claude-debug label to skip the cost gate Debugging why the paid review isn't behaving as expected (e.g. the missing --comment flag that silently swallowed every review before #57) previously meant either waiting 5-20+ min per iteration for the cost gate to clear, or hand-editing the workflow just to see the raw transcript. Adding the "claude-debug" label to a PR now (a) skips the cost gate entirely, and (b) sets show_full_output: true so the full raw Claude Code JSON transcript, including tool results, lands in the job log. The label is checked live via `gh pr view` rather than the static event payload, so adding it and clicking "Re-run jobs" picks it up without a new push. `labeled` is added as a trigger type so applying the label alone kicks off a fresh run; the job's `if:` scopes that to only the claude-debug label itself, so tagging a PR with anything else doesn't trigger another paid review. --- .github/workflows/claude-code-review.yml | 42 +++++++++++++++++++++--- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 8a4a758..3d128f4 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -18,7 +18,12 @@ name: Claude Code Review # breaks that fetch with "couldn't find remote ref pull//head". on: pull_request_target: - types: [opened, synchronize, reopened, ready_for_review] + # labeled: lets adding the claude-debug label (see the "Check for + # claude-debug label" step below) kick off a fresh run by itself, with no + # push/re-run needed. Scoped in the job's `if:` below to only actually + # proceed when the label added IS claude-debug -- otherwise every + # unrelated label added to a PR would trigger another paid review. + types: [opened, synchronize, reopened, ready_for_review, labeled] concurrency: group: claude-review-${{ github.event.pull_request.number }} @@ -36,7 +41,8 @@ jobs: # 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' + github.event.pull_request.head.repo.owner.login == 'jnasbyupgrade' && + (github.event.action != 'labeled' || github.event.label.name == 'claude-debug') runs-on: ubuntu-latest timeout-minutes: 60 permissions: @@ -51,6 +57,28 @@ jobs: # existing trusted-fork-owner gate below, not instead of it. actions: write steps: + # DEBUG MODE: add the "claude-debug" label to a PR to (a) skip the cost + # gate below entirely -- a debug session shouldn't wait 5-20+ min per + # iteration on sibling CI -- and (b) get show_full_output: true on the + # Run Claude Code Review step, dumping the full raw Claude Code JSON + # transcript (including tool results -- see that input's own WARNING + # below) to the job log. This is how you'd catch something like a + # silently-swallowed `--comment` flag (see that step's other comment). + # Queried live via `gh pr view`, not the static event payload, so + # adding the label and clicking "Re-run jobs" on an existing run picks + # it up without needing a new push. + - name: Check for claude-debug label + id: debug + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + PR: ${{ github.event.pull_request.number }} + run: | + enabled=$(gh pr view "$PR" --repo "$REPO" --json labels \ + --jq 'any(.labels[]; .name == "claude-debug")') + echo "enabled=$enabled" >> "$GITHUB_OUTPUT" + echo "claude-debug label present: $enabled" + # 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. # If any sibling check failed we skip the review to avoid spending money @@ -66,6 +94,7 @@ jobs: # gate never waits on or fails because of itself. - name: Wait for CI; skip the paid review if any check failed id: gate + if: steps.debug.outputs.enabled != 'true' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} REPO: ${{ github.repository }} @@ -93,7 +122,7 @@ jobs: echo "gate decision: $decision" - name: Check out base branch - if: steps.gate.outputs.decision == 'run' + if: steps.debug.outputs.enabled == 'true' || steps.gate.outputs.decision == 'run' # Intentionally tracks the major-version tag (not a pinned SHA) so # upstream fixes are picked up automatically. # @@ -107,10 +136,15 @@ jobs: persist-credentials: false - name: Run Claude Code Review - if: steps.gate.outputs.decision == 'run' + if: steps.debug.outputs.enabled == 'true' || steps.gate.outputs.decision == 'run' uses: anthropics/claude-code-action@v1 with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + # See the "Check for claude-debug label" step above -- WARNING (from + # this input's own description): outputs ALL Claude messages + # including tool execution results, which may contain secrets, and + # these logs are publicly visible in GitHub Actions. + show_full_output: ${{ steps.debug.outputs.enabled == 'true' }} # Provide github_token so the action uses it directly for GitHub API # calls instead of the OIDC->GitHub-App-token exchange, which 401s under # pull_request_target. GITHUB_TOKEN is repo/workflow-scoped (independent From f6efd1e1e6d074d3d6b2cc4f1a8984d2f9d2dba8 Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Sat, 1 Aug 2026 18:17:28 -0500 Subject: [PATCH 2/4] claude-code-review.yml: fix labeled events cancelling an in-progress review Caught by claude-review itself on this PR (review comment https://github.com/Postgres-Extensions/cat_tools/pull/64#discussion_r3696800099): concurrency-group cancellation is resolved when a run is admitted, before the job's `if:` is ever evaluated -- the `if:` can only no-op the new run's job, it can't un-cancel whatever the run's mere existence already displaced. Since `labeled` became a trigger type, a group keyed only on PR number meant ANY label (not just claude-debug) admitted a new run here, cancelling a real in-progress review (triggered by `synchronize`) with nothing to replace it. Fix: only give a labeled event its own per-label group when the label is NOT claude-debug, so it can never collide with (and thus never cancel) the real review's group. A labeled+claude-debug event deliberately keeps the plain group -- it's meant to supersede an in-progress real review, not run alongside it. --- .github/workflows/claude-code-review.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 3d128f4..f2b9280 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -26,7 +26,19 @@ on: types: [opened, synchronize, reopened, ready_for_review, labeled] concurrency: - group: claude-review-${{ github.event.pull_request.number }} + # BUG (found by claude-review itself, PR #64 review comment): concurrency + # cancellation is resolved when the run is admitted, BEFORE the job's `if:` + # is ever evaluated -- a job's `if:` can only no-op that job, it can't + # un-cancel whatever the run's mere existence already displaced. Since + # `labeled` is now a trigger type (see the `on:` comment above), a group + # keyed only on PR number meant ANY label -- not just claude-debug -- would + # admit a new run here, cancelling a real in-progress review with nothing + # to replace it (the new run's job then no-ops via its `if:`, too late). + # Fix: only a labeled event whose label is NOT claude-debug gets its own + # per-label group, so it can never collide with (and thus never cancel) + # the real review's group. A labeled+claude-debug event deliberately keeps + # the plain group -- it's meant to supersede an in-progress real review. + group: claude-review-${{ github.event.pull_request.number }}${{ (github.event.action == 'labeled' && github.event.label.name != 'claude-debug') && format('-{0}', github.event.label.name) || '' }} cancel-in-progress: true jobs: From 05ff28821d43d82a4ce31e0d6a7d0e8ffe1940e9 Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Sat, 1 Aug 2026 18:24:47 -0500 Subject: [PATCH 3/4] ci: update coupling guard's expected claude-review group formula This PR just made claude-code-review.yml's concurrency group conditional (only a non-claude-debug labeled event gets its own per-label group), so verify-cancel-on-close-coupling's expected literal string (added by #63, now on master) needs to match the new formula. Confirmed via `gh pr checks 64` that this is exactly what's currently failing -- this PR's own claude-code-review.yml change is compared, under the merge ref, against master's not-yet-updated expectation. --- .github/workflows/ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e96200b..d1426e9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -393,7 +393,11 @@ jobs: errors.append("claude-code-review.yml no longer has a 'claude-review' job") else: review_group = (review.get('concurrency') or {}).get('group') - expected = "claude-review-" + D + "{{ github.event.pull_request.number }}" + expected = ( + "claude-review-" + D + "{{ github.event.pull_request.number }}" + + D + "{{ (github.event.action == 'labeled' && github.event.label.name != 'claude-debug')" + + " && format('-{0}', github.event.label.name) || '' }}" + ) if review_group != expected: errors.append(f"claude-code-review.yml concurrency.group is {review_group!r}, expected {expected!r}") From de7f925d75d70bda6aa079685b8f84fa82516e11 Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Sun, 2 Aug 2026 17:48:30 -0500 Subject: [PATCH 4/4] claude-code-review.yml: trim bug comment, tolerate transient gh failures Both caught by claude-review on this PR's own commit: - The concurrency-group comment recounted the bug's full discovery history instead of briefly stating the guard fact, per this repo's Bug Fixes convention. Trimmed to the guard fact and the fix rationale. - The claude-debug label check ran unconditionally with no fallback; under the default `bash -e`, a transient `gh pr view` failure (rate limit, blip) would hard-fail the whole claude-review job on every ordinary review, not just debug runs. Add `|| enabled=false`, matching the fallback style already used in the adjacent cost-gate step. --- .github/workflows/claude-code-review.yml | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index f2b9280..3e12912 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -26,18 +26,13 @@ on: types: [opened, synchronize, reopened, ready_for_review, labeled] concurrency: - # BUG (found by claude-review itself, PR #64 review comment): concurrency - # cancellation is resolved when the run is admitted, BEFORE the job's `if:` - # is ever evaluated -- a job's `if:` can only no-op that job, it can't - # un-cancel whatever the run's mere existence already displaced. Since - # `labeled` is now a trigger type (see the `on:` comment above), a group - # keyed only on PR number meant ANY label -- not just claude-debug -- would - # admit a new run here, cancelling a real in-progress review with nothing - # to replace it (the new run's job then no-ops via its `if:`, too late). - # Fix: only a labeled event whose label is NOT claude-debug gets its own - # per-label group, so it can never collide with (and thus never cancel) - # the real review's group. A labeled+claude-debug event deliberately keeps - # the plain group -- it's meant to supersede an in-progress real review. + # Concurrency cancellation resolves when a run is admitted, before the + # job's `if:` is evaluated -- a job's `if:` can only no-op itself, it can't + # un-cancel whatever the run already displaced. So only a labeled event + # whose label is NOT claude-debug gets its own per-label group here, + # keeping it from ever colliding with (and cancelling) the real review's + # group. labeled+claude-debug deliberately keeps the plain group, since + # it's meant to supersede an in-progress review. group: claude-review-${{ github.event.pull_request.number }}${{ (github.event.action == 'labeled' && github.event.label.name != 'claude-debug') && format('-{0}', github.event.label.name) || '' }} cancel-in-progress: true @@ -87,7 +82,7 @@ jobs: PR: ${{ github.event.pull_request.number }} run: | enabled=$(gh pr view "$PR" --repo "$REPO" --json labels \ - --jq 'any(.labels[]; .name == "claude-debug")') + --jq 'any(.labels[]; .name == "claude-debug")' 2>/dev/null) || enabled=false echo "enabled=$enabled" >> "$GITHUB_OUTPUT" echo "claude-debug label present: $enabled"