1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# Created from the nantobv/.github workflow template "Claude Code".
# Wires this repo into the nantobv reusable Claude Code responder.
#
# Prerequisites (one-time, org-level — already in place for nantobv):
# - The Anthropic Claude GitHub App installed on the org with
# "All repositories" scope.
# - An org-level Actions secret CLAUDE_CODE_OAUTH_TOKEN reachable
# from this repo.
#
# Behavior, security gate (@claude must come from OWNER/MEMBER/COLLABORATOR),
# and SHA-pinned action versions live in the reusable workflow:
# nantobv/.github/.github/workflows/claude-reusable.yml
name: Claude Code
on:
issue_comment:
types:
pull_request_review_comment:
types:
issues:
# Only `opened`. Adding `assigned` would re-fire @claude on every
# assignment change of an issue whose body/title already contains
# @claude, burning quota on routine triage actions.
types:
pull_request_review:
types:
# Caller-level permissions must be at least as broad as what the
# reusable workflow's job-level block requests, otherwise GitHub
# pre-validates and rejects the run with `startup_failure` before any
# job starts. These mirror claude-reusable.yml's `jobs.claude.permissions:`
# block one-for-one. Each is required: contents (post a comment / branch),
# issues + pull-requests (write replies), id-token (OIDC for the
# action's auth), actions read (read workflow context).
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
actions: read
jobs:
claude:
# Short-circuit before invoking the reusable: only fire when an
# @claude mention comes from OWNER/MEMBER/COLLABORATOR. Without
# this, every issue comment / PR review on this repo spins up a
# runner just to let the reusable's identical filter no-op. The
# reusable keeps the same filter as defense-in-depth.
if: |
(github.event_name == 'issue_comment' &&
contains(github.event.comment.body, '@claude') &&
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) ||
(github.event_name == 'pull_request_review_comment' &&
contains(github.event.comment.body, '@claude') &&
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) ||
(github.event_name == 'pull_request_review' &&
contains(github.event.review.body, '@claude') &&
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.review.author_association)) ||
(github.event_name == 'issues' &&
(contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')) &&
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.issue.author_association))
uses: nantobv/.github/.github/workflows/claude-reusable.yml@main # zizmor: ignore[unpinned-uses] same-org reusable workflow tracks @main by design; pinact exception declared org-wide in nantobv/.github/.pinact.yaml.
# Pass only the secret the reusable declares it needs; avoid the
# blast radius of `secrets: inherit`.
secrets:
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}