ci: restore the Claude Code workflow and .mcp.json, lost in the v2 tree swap - #1869
Conversation
Closes #1850. Restored byte-for-byte from ac3c1a1 (sha256 1c44f2d9d51c936adb40c9acefd4b44628fe7810360e2f8196034347f9d2602d, 3592 bytes). No content changes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YAt8rqxysNbhYWLhoRm3fU
The restored workflow runs with `--mcp-config .mcp.json` and `--allowedTools "Bash,mcp__mcp-docs"`, so without this file the mcp-docs server never registers and those tools do not resolve. Lost in the same tree swap: .mcp.json survives only on v1/main, and is absent from both main and v2/main. Restored byte-for-byte from 47f9284 (sha256 b0f0b8ea0d6c6e4bd9d19270db8c4ed13a951c6a7d8fa9a7b76d0d12ddd2491f, 123 bytes). No content changes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YAt8rqxysNbhYWLhoRm3fU
There was a problem hiding this comment.
Pull request overview
Restores the repository’s Claude Code GitHub Actions workflow and its .mcp.json MCP-server configuration, which were dropped during the v2 tree swap, to re-enable @claude-triggered automation once the changes reach the default branch.
Changes:
- Re-add
.github/workflows/claude.ymlto handle@claudetriggers on issue/PR events. - Re-add
.mcp.jsondefining themcp-docsHTTP MCP server used by the workflow. - Ensure the workflow continues to invoke Claude Code with the MCP config and allowed tools.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
.github/workflows/claude.yml |
Restores the Claude Code workflow that reacts to @claude across issue/PR-related events. |
.mcp.json |
Restores MCP server configuration (mcp-docs) used by the Claude workflow. |
Suppressed comments (1)
.github/workflows/claude.yml:80
- If the PR is from a fork (or PR metadata lookup fails), the workflow falls back to checking out the base repository but will still run Claude. That can lead to commenting based on the wrong code (default branch) and still consumes secrets/time. Gate the Claude step so it only runs for same-repo PRs, while still allowing non-PR issue/issue_comment triggers.
- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@v1
with:
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| contents: read | ||
| pull-requests: read | ||
| issues: read | ||
| id-token: write | ||
| actions: read |
There was a problem hiding this comment.
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 touchedclaude.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, at23:45:01Zand23:47:53Z, each ~20s after a@claude reviewcomment.
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.
| core.setOutput('repo', pr.data.head.repo.full_name); | ||
|
|
||
| - name: Checkout PR branch | ||
| if: steps.pr.outcome == 'success' |
There was a problem hiding this comment.
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_policyiscollaborators_only(confirmed viagh 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.
Closes #1850.
Recreated from #1851, which GitHub auto-closed when its head branch was renamed
ci/restore-claude-workflow→v2/ci/restore-claude-workflowto satisfy the version-prefix branch convention. Same commits, same content; #1851 could not be reopened because its original head ref no longer exists.Same regression class as #1843 (
SECURITY.md): the v2 tree swap replaced the default branch's tree, and neither.github/workflows/claude.ymlnor.mcp.jsonexisted in the v2 tree, so both were silently dropped.Impact
issue_comment/pull_request_review_comment/issuesevents always run workflows from the default branch, so losing the workflow disabled@clauderepo-wide — including on PRs targetingv2/main.Found the hard way: two
@claude reviewcomments on #1847 (19:08 and 19:10 UTC) produced no bot reply and no workflow run.Evidence
actions/workflows/173749385statedeletedissue_comment, latest 03:10 UTC today)skipped.github/workflows/onmainmain.ymlac3c1a12(last commit carrying the file)mainThe change
Two files, both restored byte-for-byte, both verified after the copy:
.github/workflows/claude.ymlac3c1a121c44f2d9d51c936adb40c9acefd4b44628fe7810360e2f8196034347f9d2602d.mcp.json47f92841(v1/main)b0f0b8ea0d6c6e4bd9d19270db8c4ed13a951c6a7d8fa9a7b76d0d12ddd2491fNo content changes to either. The
@claudetrigger remains gated toOWNER/MEMBER/COLLABORATORauthor associations, which is the behavior we want under the issues-only contribution model (#1820).Why
.mcp.jsonis in the same PRThe workflow runs with
--mcp-config .mcp.jsonand--allowedTools "Bash,mcp__mcp-docs"..mcp.jsonregisters themcp-docsHTTP server (https://modelcontextprotocol.io/mcp) and was lost in the same swap — it survives only onv1/main:Restoring the workflow without it would register no MCP server, so the
mcp__mcp-docstools would not resolve and the protocol-documentation lookups that--append-system-promptexplicitly directs it to make would silently do nothing. Shipping them together avoids a window where the workflow is onv2/mainpointing at a file that isn't.Why this targets
v2/mainand notmainThis work originally targeted
main. It now targetsv2/main, the develop branch that merges intomainat milestone releases. Landing onv2/mainis what makes the fix durable: restoring only tomainwould be undone by the next milestone merge, which is precisely how these files were lost.Verifying after merge
@claudecannot work until this reachesmain, which happens at the next milestone merge — a workflow on a non-default branch is not consulted forissue_commentevents. After that, comment@claude reviewon any open PR;claude[bot]should reply within ~20s (it took 19s on #1825). If it's needed before the milestone, that wants a separate direct-to-mainPR in addition to this one.Testing
No source files are touched, so nothing in
validateorcoveragemeasures this change — the format globs covercore/,scripts/, the shared surface, and each client's scope, none of which reach a workflow YAML or a root-level JSON.npx prettier --check .mcp.jsonpasses.Note
SECURITY.md(#1843, plus #1864/#1867 for thev2/mainhalf),claude.yml, and.mcp.jsonare three instances of one pattern, and none was caught by CI. A sweep for anything else the swap dropped is worth doing separately — and, contrary to #1851's closing note, git can surface these directly, since the branches share history:git diff --stat origin/main origin/v2/mainlists the divergence.🤖 Generated with Claude Code
https://claude.ai/code/session_01YAt8rqxysNbhYWLhoRm3fU