99 types : [opened, assigned]
1010 pull_request_review :
1111 types : [submitted]
12+ pull_request_target :
13+ types : [opened, synchronize]
1214
1315jobs :
1416 claude :
1517 if : |
16- (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
17- (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
18- (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
19- (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
20-
18+ (
19+ (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
20+ (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
21+ (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
22+ (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) ||
23+ (github.event_name == 'pull_request_target' && contains(github.event.pull_request.body, '@claude'))
24+ ) && (
25+ github.event.sender.author_association == 'OWNER' ||
26+ github.event.sender.author_association == 'MEMBER' ||
27+ github.event.sender.author_association == 'COLLABORATOR' ||
28+ github.event.pull_request.author_association == 'OWNER' ||
29+ github.event.pull_request.author_association == 'MEMBER' ||
30+ github.event.pull_request.author_association == 'COLLABORATOR'
31+ )
2132 runs-on : ubuntu-latest
2233 permissions :
2334 contents : read
2435 pull-requests : read
2536 issues : read
2637 id-token : write
38+ actions : read # Required for Claude to read CI results on PRs
2739 steps :
28- - name : Check user permissions
29- id : check_membership
30- uses : actions/github-script@v7
31- with :
32- script : |
33- let actor;
34- if (context.eventName === 'issue_comment') {
35- actor = context.payload.comment.user.login;
36- } else if (context.eventName === 'pull_request_review_comment') {
37- actor = context.payload.comment.user.login;
38- } else if (context.eventName === 'pull_request_review') {
39- actor = context.payload.review.user.login;
40- } else if (context.eventName === 'issues') {
41- actor = context.payload.issue.user.login;
42- }
43-
44- console.log(`Checking permissions for user: ${actor}`);
45-
46- // List of explicitly allowed users (organization members)
47- const allowedUsers = [
48- 'phernandez',
49- 'groksrc',
50- 'nellins',
51- 'bm-claudeai'
52- ];
53-
54- if (allowedUsers.includes(actor)) {
55- console.log(`User ${actor} is in the allowed list`);
56- core.setOutput('is_member', true);
57- return;
58- }
59-
60- // Fallback: Check if user has repository permissions
61- try {
62- const collaboration = await github.rest.repos.getCollaboratorPermissionLevel({
63- owner: context.repo.owner,
64- repo: context.repo.repo,
65- username: actor
66- });
67-
68- const permission = collaboration.data.permission;
69- console.log(`User ${actor} has permission level: ${permission}`);
70-
71- // Allow if user has push access or higher (write, maintain, admin)
72- const allowed = ['write', 'maintain', 'admin'].includes(permission);
73-
74- core.setOutput('is_member', allowed);
75-
76- if (!allowed) {
77- core.notice(`User ${actor} does not have sufficient repository permissions (has: ${permission})`);
78- }
79- } catch (error) {
80- console.log(`Error checking permissions: ${error.message}`);
81-
82- // Final fallback: Check if user is a public member of the organization
83- try {
84- const membership = await github.rest.orgs.getMembershipForUser({
85- org: 'basicmachines-co',
86- username: actor
87- });
88-
89- const allowed = membership.data.state === 'active';
90- core.setOutput('is_member', allowed);
91-
92- if (!allowed) {
93- core.notice(`User ${actor} is not a public member of basicmachines-co organization`);
94- }
95- } catch (membershipError) {
96- console.log(`Error checking organization membership: ${membershipError.message}`);
97- core.setOutput('is_member', false);
98- core.notice(`User ${actor} does not have access to this repository`);
99- }
100- }
101-
10240 - name : Checkout repository
103- if : steps.check_membership.outputs.is_member == 'true'
10441 uses : actions/checkout@v4
10542 with :
43+ # For pull_request_target, checkout the PR head to review the actual changes
44+ ref : ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || github.sha }}
10645 fetch-depth : 1
10746
10847 - name : Run Claude Code
109- if : steps.check_membership.outputs.is_member == 'true'
11048 id : claude
111- uses : anthropics/claude-code-action@beta
49+ uses : anthropics/claude-code-action@v1
11250 with :
113- anthropic_api_key : ${{ secrets.ANTHROPIC_API_KEY }}
114- allowed_tools : Bash(uv run pytest),Bash(uv run ruff check . --fix),Bash(uv run ruff format .),Bash(uv run pyright),Bash(just test),Bash(just lint),Bash(just format),Bash(just type-check),Bash(just check),Read,Write,Edit,MultiEdit,Glob,Grep,LS, mcp__web_search
51+ claude_code_oauth_token : ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
52+ track_progress : true # Enable visual progress tracking
53+
54+ # This is an optional setting that allows Claude to read CI results on PRs
55+ additional_permissions : |
56+ actions: read
57+
58+ # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
59+ # prompt: 'Update the pull request description to include a summary of changes.'
60+
61+ # Optional: Add claude_args to customize behavior and configuration
62+ # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
63+ # or https://docs.claude.com/en/docs/claude-code/sdk#command-line for available options
64+ # claude_args: '--model claude-opus-4-1-20250805 --allowed-tools Bash(gh pr:*)'
65+
0 commit comments