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
93 changes: 93 additions & 0 deletions .github/workflows/claude.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
name: Claude Code

on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]

jobs:
claude:
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))
)
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
actions: read
Comment on lines +32 to +36

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • >

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not taking this one — the premise is factually incorrect, and acting on it would weaken the workflow's security posture for no gain.

anthropics/claude-code-action does not post through GITHUB_TOKEN. It exchanges the OIDC token granted by id-token: write (present on line 35) for a Claude Code GitHub App installation token, and that app token carries its own write scopes. The permissions: block on lines 31-36 scopes GITHUB_TOKEN only, so read-only grants there do not gate comment or review posting.

Verified empirically rather than by reading docs:

  • ac3c1a12, the commit this file is restored byte-for-byte from, is dated 2026-07-17 and is the last commit that touched claude.yml.
  • claude[bot] posted on docs: refresh V2 board Status option IDs in AGENTS.md (#1823) #1825 on 2026-07-27 — ten days later, so this exact permissions block was live — twice, at 23:45:01Z and 23:47:53Z, each ~20s after a @claude review comment.

So @claude demonstrably replied under issues: read / pull-requests: read. The failure this PR fixes was the workflow file being absent from the default branch after the v2 tree swap (actions/workflows/173749385 is in state deleted), not a permissions problem.

Widening these to write would hand a broader GITHUB_TOKEN to a job that runs a tool-enabled agent with Bash allowed, which is exactly the grant worth keeping minimal. Leaving them read-only.

steps:
- name: Get PR details
if: |
(github.event_name == 'issue_comment' && github.event.issue.pull_request) ||
github.event_name == 'pull_request_review_comment' ||
github.event_name == 'pull_request_review'
id: pr
uses: actions/github-script@v8
with:
script: |
let prNumber;
if (context.eventName === 'issue_comment') {
prNumber = context.issue.number;
} else {
prNumber = context.payload.pull_request.number;
}

const pr = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});

core.setOutput('sha', pr.data.head.sha);
core.setOutput('repo', pr.data.head.repo.full_name);

- name: Checkout PR branch
if: steps.pr.outcome == 'success'

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Legitimate class of concern, and I'm not dismissing it — but deferring it to its own PR rather than folding it in here. Tracked as #1882 (on the v2 board), which also captures your suppressed comment about the fallback branch reviewing the base tree while still spending secrets.

Two reasons it isn't changing in this PR:

1. This PR's contract is a byte-for-byte restore. Both files are restored unmodified from the commits that last carried them (claude.yml from ac3c1a12, sha256 1c44f2d9…2602d, 3592 bytes; .mcp.json from 47f92841, sha256 b0f0b8ea…491f, 123 bytes). The value of that is that the restored workflow is a known-working artifact — it was live and answering @claude through 2026-07-27. Mixing a behavior change into the restore means a post-merge failure has two candidate causes instead of zero, and this workflow can only be exercised from the default branch, so it isn't testable until after it lands.

2. The exposure is already structurally mitigated here.

  • Every trigger arm requires contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), …author_association), so an outside contributor cannot self-trigger the workflow on their own PR. That gate is intentional under the issues-only contribution model (go-live 6/9: restrict PRs to collaborators; CONTRIBUTORS.md + SECURITY.md #1820) and is staying.
  • The repo's pull_request_creation_policy is collaborators_only (confirmed via gh api repos/modelcontextprotocol/inspector), so PRs from non-collaborators cannot be opened at all.

The residual path is a maintainer deliberately invoking @claude on a collaborator's fork branch — and collaborators already hold repo access, so the marginal exposure over the status quo is small. Real, worth closing, not urgent enough to compromise the restore.

#1882 carries the candidate fixes: gating the head checkout on head.repo.full_name == github.repository, and/or gating the Run Claude Code step itself on same-repo so the fallback can't review the wrong tree.

uses: actions/checkout@v6
with:
ref: ${{ steps.pr.outputs.sha }}
repository: ${{ steps.pr.outputs.repo }}
fetch-depth: 0

- name: Checkout repository
if: steps.pr.outcome != 'success'
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}

# Allow Claude to read CI results on PRs
additional_permissions: |
actions: read

# Trigger when assigned to an issue
assignee_trigger: "claude"

claude_args: |
--mcp-config .mcp.json
--allowedTools "Bash,mcp__mcp-docs"
--append-system-prompt "If posting a comment to GitHub, give a concise summary of the comment at the top and put all the details in a <details> block. When working on MCP-related code or reviewing MCP-related changes, use the mcp-docs MCP server to look up the latest protocol documentation. For schema details, reference https://github.com/modelcontextprotocol/modelcontextprotocol/tree/main/schema which contains versioned schemas in JSON (schema.json) and TypeScript (schema.ts) formats."
8 changes: 8 additions & 0 deletions .mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"mcpServers": {
"mcp-docs": {
"type": "http",
"url": "https://modelcontextprotocol.io/mcp"
}
}
}
Loading