From 444e6bf6a30af0dfe3f1193e8f7b7e339e8f0f24 Mon Sep 17 00:00:00 2001 From: phernandez Date: Thu, 16 Jul 2026 15:56:23 -0500 Subject: [PATCH 1/2] fix(ci): let @claude self-verify and document CI-on-bot-PR gap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two follow-ups to #1087, which unblocked @claude from opening PRs: 1. Provision python/uv/just + install deps in the claude job (mirroring test.yml's known-good setup) so a run can execute the project quality gates before pushing. Without a toolchain the runner has no just/uv, so a run wastes its whole time budget on pip workarounds and can time out AFTER writing the code but BEFORE committing — exactly what killed the first #1011 run at 25m, losing the work with the ephemeral runner. 2. Document the known limitation that @claude-authored PRs get no CI: the job pushes with GITHUB_TOKEN, GitHub does not fire push-triggered workflows for GITHUB_TOKEN pushes, and test.yml is push-only, so a claude/** branch never runs the test suite. The self-verify step is a partial mitigation; the proper fix is pushing with a PAT/App token as the action's github_token (a repo-admin task — create the token + secret), noted inline. Signed-off-by: phernandez --- .github/workflows/claude.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index af2c58de2..0af5dbcab 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -1,5 +1,15 @@ name: Claude Code +# KNOWN LIMITATION — CI does not run on @claude-authored PRs. +# This job pushes the working branch with the default GITHUB_TOKEN. GitHub does not +# fire `push`-triggered workflows for commits pushed by GITHUB_TOKEN, and test.yml is +# push-only, so a claude/** branch never runs the test suite — the PR looks mergeable +# but is unvalidated. The setup steps below let @claude self-verify in-run, which is a +# partial mitigation. The proper fix is to push with a PAT or GitHub App token: create a +# fine-grained token with contents+pull-requests write, store it as a secret, and pass it +# to the action as `github_token`. Until then, verify @claude PRs locally (or re-push the +# branch with a human token to trigger test.yml) before merging. + on: issue_comment: types: [created] @@ -53,6 +63,26 @@ jobs: ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || github.sha }} fetch-depth: 1 + # Provision the toolchain @claude needs to self-verify a change before pushing. + # Without this the runner has no just/uv, so a run burns its whole time budget on + # pip workarounds and can time out AFTER writing the code but BEFORE committing + # (that is what happened on the first #1011 run). Mirrors test.yml's known-good setup. + - name: Set up Python 3.12 + uses: actions/setup-python@v6 + with: + python-version: "3.12" + cache: "pip" + + - name: Install uv + run: pip install uv + + - uses: extractions/setup-just@v4 + + - name: Create virtual env and install dependencies + run: | + uv venv + uv pip install -e ".[dev]" + - name: Run Claude Code id: claude uses: anthropics/claude-code-action@v1 From 3d964bb3c983bbd63888f2b51dfcd77b9385d262 Mon Sep 17 00:00:00 2001 From: phernandez Date: Thu, 16 Jul 2026 18:41:47 -0500 Subject: [PATCH 2/2] fix(ci): drop pull_request_target trigger to stop @claude OIDC 401s MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Anthropic's OIDC exchange is broken for the pull_request_target event (401 'Invalid OIDC token'; anthropics/claude-code-action#713). The workflow invoked that path whenever a PR body contained '@claude' — and bot-authored PR descriptions carry attribution text ('Generated by the @claude workflow'), so opening #1088/#1089/#1090/#1091 self-triggered the broken path and each failed its 'claude' check with a 401. A normal issue_comment run succeeded with the same secret in between, so the OAuth token is valid and needs no rotation. Remove the pull_request_target trigger and its PR-body '@claude' clause, so attribution text can never be read as a command and no event routes into the broken OIDC path. Explicit '@claude' mentions in issues, issue comments, PR reviews, and PR review comments remain the trigger. Also drop the now-dead pull_request.author_association gate clauses (reviewer identity is covered by sender.author_association) and the pull_request_target-only checkout ref. If automatic PR-open review is wanted later, it should be a dedicated workflow with a fixed prompt, not a PR-body mention. Signed-off-by: phernandez --- .github/workflows/claude.yml | 39 ++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 0af5dbcab..4e5d20606 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -1,14 +1,23 @@ name: Claude Code -# KNOWN LIMITATION — CI does not run on @claude-authored PRs. -# This job pushes the working branch with the default GITHUB_TOKEN. GitHub does not -# fire `push`-triggered workflows for commits pushed by GITHUB_TOKEN, and test.yml is -# push-only, so a claude/** branch never runs the test suite — the PR looks mergeable -# but is unvalidated. The setup steps below let @claude self-verify in-run, which is a -# partial mitigation. The proper fix is to push with a PAT or GitHub App token: create a -# fine-grained token with contents+pull-requests write, store it as a secret, and pass it -# to the action as `github_token`. Until then, verify @claude PRs locally (or re-push the -# branch with a human token to trigger test.yml) before merging. +# @claude runs only when a maintainer (OWNER/MEMBER/COLLABORATOR) explicitly mentions the +# bot in an issue, an issue comment, a PR review, or a PR review comment. It deliberately +# does NOT trigger on `pull_request_target`: +# - Anthropic's OIDC exchange is broken for that event (401 "Invalid OIDC token"; +# anthropics/claude-code-action#713), and +# - a PR-body `@claude` check self-triggered it, because bot-authored PR descriptions +# carry attribution text like "Generated by the @claude workflow". Attribution text +# must never be interpreted as a command. +# If automatic PR-open reviews are wanted later, add a dedicated workflow with a fixed +# prompt rather than reading a PR body for `@claude`. +# +# Separate known limitation — CI does not auto-run on @claude-authored PRs. The bot pushes +# its branch with GITHUB_TOKEN, GitHub does not fire `push` workflows for GITHUB_TOKEN +# pushes, and test.yml is push-only, so a claude/** branch never runs the test suite and +# the PR looks mergeable while unvalidated. The setup steps below let @claude self-verify +# in-run (partial mitigation); still verify @claude PRs locally, or re-push the branch with +# a human token to trigger test.yml, before merging. Proper fix: push with a PAT/App token +# as the action's github_token. on: issue_comment: @@ -19,8 +28,6 @@ on: types: [opened, assigned] pull_request_review: types: [submitted] - pull_request_target: - types: [opened, synchronize] env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" @@ -32,18 +39,14 @@ jobs: (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'))) || - (github.event_name == 'pull_request_target' && contains(github.event.pull_request.body, '@claude')) + (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) ) && ( github.event.comment.author_association == 'OWNER' || github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'COLLABORATOR' || github.event.sender.author_association == 'OWNER' || github.event.sender.author_association == 'MEMBER' || - github.event.sender.author_association == 'COLLABORATOR' || - github.event.pull_request.author_association == 'OWNER' || - github.event.pull_request.author_association == 'MEMBER' || - github.event.pull_request.author_association == 'COLLABORATOR' + github.event.sender.author_association == 'COLLABORATOR' ) runs-on: ubuntu-latest permissions: @@ -59,8 +62,6 @@ jobs: - name: Checkout repository uses: actions/checkout@v6 with: - # For pull_request_target, checkout the PR head to review the actual changes - ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || github.sha }} fetch-depth: 1 # Provision the toolchain @claude needs to self-verify a change before pushing.