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
6 changes: 6 additions & 0 deletions .github/workflows/check-pr-checklist.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ permissions:

jobs:
check-checklist:
# Only validate checklists on PRs from trusted accounts. Outsider PRs
# require maintainer approval to run any workflow; the maintainer reads
# the PR body manually before approving.
if: >-
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association)
|| github.event.pull_request.user.login == 'claude[bot]'
runs-on: ubuntu-latest
steps:
- name: Validate Checklist
Expand Down
25 changes: 22 additions & 3 deletions .github/workflows/claude-implement-fixes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,37 @@ concurrency:

jobs:
implement:
# Trust gate per event source: only org owners/members/collaborators (or
# claude[bot]) may trigger `@claude fix`. This workflow has
# `contents: write` + bypassPermissions, so the blast radius for a wrong
# allow is high. The body-match `@claude fix` and the
# pull_request_review-late-gate (in the `Gate on @claude fix presence`
# step) are unchanged. The previous `user.type != 'Bot'` checks are
# subsumed by the positive author_association allowlist.
if: >-
(
github.event_name == 'issue_comment'
&& github.event.issue.pull_request != null
&& contains(github.event.comment.body, '@claude fix')
&& github.event.comment.user.type != 'Bot'
&& (
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)
|| github.event.comment.user.login == 'claude[bot]'
)
)
|| (
github.event_name == 'pull_request_review_comment'
&& contains(github.event.comment.body, '@claude fix')
&& github.event.comment.user.type != 'Bot'
&& (
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)
|| github.event.comment.user.login == 'claude[bot]'
)
)
|| (
github.event_name == 'pull_request_review'
&& github.event.review.user.type != 'Bot'
&& (
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.review.author_association)
|| github.event.review.user.login == 'claude[bot]'
)
)
runs-on: ubuntu-latest
timeout-minutes: 60
Expand Down Expand Up @@ -99,6 +115,9 @@ jobs:
if: steps.gate.outputs.should_run == 'true'
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
# Match the bot allowlist in the job-level `if:` above. Never `*` on
# a public repo (per the action's docs warning).
allowed_bots: "claude[bot]"
claude_args: |
--model ${{ vars.CLAUDE_MODEL || 'claude-opus-4-7[1m]' }}
--permission-mode bypassPermissions
Expand Down
24 changes: 18 additions & 6 deletions .github/workflows/claude-pr-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,22 @@ permissions:

jobs:
review:
# Skip on `synchronize` when the pusher is a Claude bot — otherwise the
# implement-fixes workflow's pushes would re-trigger us in a loop and we'd
# end up reviewing our own fixes. Always run on `opened` (auto-reviewing a
# Claude-opened PR is the whole point of this workflow).
# Two gates AND'd together:
# (1) Trust gate — only org owners/members/collaborators or claude[bot]
# (which opens `chore(claude): learn from #N` PRs). author_association
# is NONE for bots, so the login OR is required.
# (2) Loop gate — on `synchronize` triggered by claude[bot] pushing a
# fix commit (from claude-implement-fixes.yml), skip; otherwise we'd
# review our own fixes ad infinitum. `opened` always passes.
if: >-
github.event.action == 'opened'
|| !contains(github.event.sender.login, 'claude')
(
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association)
|| github.event.pull_request.user.login == 'claude[bot]'
)
&& (
github.event.action == 'opened'
|| github.event.sender.login != 'claude[bot]'
)
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
Expand All @@ -49,6 +58,9 @@ jobs:
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
# Match the bot allowlist in the job-level `if:` above. Never `*` on
# a public repo (per the action's docs warning).
allowed_bots: "claude[bot]"
claude_args: |
--model ${{ vars.CLAUDE_MODEL || 'claude-opus-4-7[1m]' }}
--permission-mode bypassPermissions
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/cpu_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ env:

jobs:
cpu-tests:
# Block PRs from non-org accounts to keep fork code off our runners.
# `push` and `workflow_dispatch` already require write access, so the
# short-circuit lets them through unchanged.
if: >-
github.event_name != 'pull_request'
|| contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association)
|| github.event.pull_request.user.login == 'claude[bot]'
name: CPU Tests
runs-on: ubuntu-latest
steps:
Expand Down
20 changes: 16 additions & 4 deletions .github/workflows/extract-claude-lessons.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,23 @@ permissions:

jobs:
extract-lessons:
# Gate on the head branch name, not user.login: in this repo Claude Code
# pushes to a `claude/*` branch and a human opens the PR, so the PR's
# `user.login` never contains 'claude'. The branch prefix is the reliable
# signal that Claude touched the PR.
# Four AND'd gates:
# (1) PR was merged.
# (2) Head branch is `claude/*` (Claude Code touched it). Per #212,
# this is the reliable signal even when a human opens the PR.
# (3) Title isn't already a learn-from-N PR (avoid recursion).
# (4) Trust: PR opener is org owner/member/collaborator OR claude[bot].
# Defense-in-depth against an outside collaborator naming their
# fork branch `claude/...` and getting a merge through.
# author_association is NONE for bots, so the login OR is required.
if: >-
github.event.pull_request.merged == true
&& startsWith(github.event.pull_request.head.ref, 'claude/')
&& !startsWith(github.event.pull_request.title, 'chore(claude): learn from')
&& (
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association)
|| github.event.pull_request.user.login == 'claude[bot]'
)
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
Expand All @@ -42,6 +51,9 @@ jobs:
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
# Match the bot allowlist in the job-level `if:` above. Never `*` on
# a public repo (per the action's docs warning).
allowed_bots: "claude[bot]"
claude_args: |
--model ${{ vars.CLAUDE_MODEL || 'claude-opus-4-7[1m]' }}
--permission-mode bypassPermissions
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/pre-commit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ permissions:

jobs:
pre-commit:
# Block PRs from non-org accounts to keep fork code off our runners.
# `push` already requires write access, so the short-circuit lets it
# through unchanged.
if: >-
github.event_name != 'pull_request'
|| contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association)
|| github.event.pull_request.user.login == 'claude[bot]'
name: Pre-commit Hooks
runs-on: ubuntu-latest
steps:
Expand Down
Loading