Summary
On pull_request events with anthropics/claude-code-action@v1 (SHA 4e5d8b13ca281a6d163cdb287d8917b216e00d6f), the action's bundled MCP servers — specifically github_comment and github_inline_comment — never spawn in the Claude CLI subprocess. Claude Code's session init message reports "mcp_servers": [] on every run. The action correctly builds --mcp-config '{…}' via src/mcp/install-mcp-server.ts (verified by reading the source), but the spawned Claude CLI either doesn't receive or doesn't consume it. No startup error is logged — just silent absence.
This is reproducible across 10+ iterations with varying config (documented below).
Environment
- Runner: GitHub-hosted
ubuntu-24.04
- Bun 1.3.6 (installed by action)
- Claude CLI:
/home/runner/.local/bin/claude (installed by action)
- Auth:
claude_code_oauth_token (subscription OAuth)
- Event:
pull_request (synchronize, same-repo branch, not a fork)
- Repo: private
Reproduction
Minimal workflow that triggers the bug:
jobs:
claude_review:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
pull-requests: write
issues: write
id-token: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
track_progress: true
prompt: |
<custom senior-dev review instructions>
Expected behavior
- Action detects tag mode (✅ confirmed in logs:
Auto-detected mode: tag for event: pull_request)
prepareMcpConfig builds JSON config containing github_comment server (✅ code path verified in src/mcp/install-mcp-server.ts)
tag/index.ts appends --mcp-config '{…}' to claudeArgs (✅ confirmed)
parse-sdk-options.ts keeps mcp-config in extraArgs for forwarding to the CLI (✅ confirmed)
- Claude CLI session init includes the server in
mcp_servers
- Claude can call
mcp__github_comment__update_claude_comment to update the tracking comment
Actual behavior
Auto-detected mode: tag for event: pull_request ✅
- SDK options
allowedTools contains mcp__github_comment__update_claude_comment ✅
- Claude Code's init message:
{
"type": "system",
"subtype": "init",
"tools": [ "Task", "Bash", "Edit", "Read", ... ],
"mcp_servers": []
}
- Claude attempts
mcp__github_comment__update_claude_comment → Error: No such tool available: mcp__github_comment__update_claude_comment
- Claude falls back to trying
gh api / gh pr comment — all denied (not in allowlist)
- Final sticky comment never updates from the action's
"I'll analyze this..." placeholder
Full SDK output (show_full_output: true) confirms Claude repeatedly attempts the advertised MCP tool and gets No such tool available. The tool name is in allowedTools but the backing server isn't registered.
What we tried (none fixed it)
track_progress: true (forces tag mode — confirmed active)
- Explicit MCP tool names in
claude_args --allowed-tools
- Adding
actions: read permission (fixed unrelated github_ci warning; github_comment still missing)
settings: "{}" override (made it worse — permission_denials_count went from 2 → 13)
claude_args: "--setting-sources user" to bypass the project's .claude/settings.json (also made it worse)
- Custom prompt removed vs kept (no difference to MCP state)
- Various combinations of the above
In every configuration, "mcp_servers": [] persists.
Suspicion
The disconnect seems to sit between how the action builds claudeArgs and how @anthropic-ai/claude-agent-sdk's query() forwards extraArgs to the spawned Claude CLI subprocess. The --mcp-config flag appears to be dropped or not consumed somewhere in that chain.
From src/modes/tag/index.ts:
const ourMcpConfig = await prepareMcpConfig({ …, mode: "tag", … });
claudeArgs = `--mcp-config '${escapedOurConfig}'`;
claudeArgs += ` --permission-mode acceptEdits --allowedTools "${tagModeTools.join(",")}"`;
if (userClaudeArgs) { claudeArgs += ` ${userClaudeArgs}`; }
From base-action/src/parse-sdk-options.ts:
// Pass through claudeArgs as extraArgs - CLI handles --mcp-config, --json-schema, etc.
extraArgs,
The code path is correct; the runtime outcome isn't.
Workaround (for anyone finding this)
We shipped a wrapper script instead. Claude writes /tmp/claude-review.json via the Write tool (path-scoped), and a separate workflow step runs a bash script that validates the JSON and posts via POST /repos/{owner}/{repo}/pulls/{n}/reviews. Happy to share the workflow as a gist if useful.
Asks
- Can a maintainer confirm whether
--mcp-config from extraArgs is supposed to reach the spawned Claude CLI when using @anthropic-ai/claude-agent-sdk's query()?
- If yes, any known environment where it silently drops (e.g., specific Bun version, specific settings file shape, OAuth vs API-key auth path)?
- If no, what's the intended mechanism for the bundled MCP servers to be registered in the session?
Happy to provide full run logs (multiple, with show_full_output: true enabled) on request. I've redacted nothing that's sensitive but didn't want to paste hundreds of kB inline.
Summary
On
pull_requestevents withanthropics/claude-code-action@v1(SHA4e5d8b13ca281a6d163cdb287d8917b216e00d6f), the action's bundled MCP servers — specificallygithub_commentandgithub_inline_comment— never spawn in the Claude CLI subprocess. Claude Code's session init message reports"mcp_servers": []on every run. The action correctly builds--mcp-config '{…}'viasrc/mcp/install-mcp-server.ts(verified by reading the source), but the spawned Claude CLI either doesn't receive or doesn't consume it. No startup error is logged — just silent absence.This is reproducible across 10+ iterations with varying config (documented below).
Environment
ubuntu-24.04/home/runner/.local/bin/claude(installed by action)claude_code_oauth_token(subscription OAuth)pull_request(synchronize, same-repo branch, not a fork)Reproduction
Minimal workflow that triggers the bug:
Expected behavior
Auto-detected mode: tag for event: pull_request)prepareMcpConfigbuilds JSON config containinggithub_commentserver (✅ code path verified insrc/mcp/install-mcp-server.ts)tag/index.tsappends--mcp-config '{…}'toclaudeArgs(✅ confirmed)parse-sdk-options.tskeepsmcp-configinextraArgsfor forwarding to the CLI (✅ confirmed)mcp_serversmcp__github_comment__update_claude_commentto update the tracking commentActual behavior
Auto-detected mode: tag for event: pull_request✅allowedToolscontainsmcp__github_comment__update_claude_comment✅{ "type": "system", "subtype": "init", "tools": [ "Task", "Bash", "Edit", "Read", ... ], "mcp_servers": [] }mcp__github_comment__update_claude_comment→Error: No such tool available: mcp__github_comment__update_claude_commentgh api/gh pr comment— all denied (not in allowlist)"I'll analyze this..."placeholderFull SDK output (
show_full_output: true) confirms Claude repeatedly attempts the advertised MCP tool and getsNo such tool available. The tool name is inallowedToolsbut the backing server isn't registered.What we tried (none fixed it)
track_progress: true(forces tag mode — confirmed active)claude_args --allowed-toolsactions: readpermission (fixed unrelatedgithub_ciwarning;github_commentstill missing)settings: "{}"override (made it worse —permission_denials_countwent from 2 → 13)claude_args: "--setting-sources user"to bypass the project's.claude/settings.json(also made it worse)In every configuration,
"mcp_servers": []persists.Suspicion
The disconnect seems to sit between how the action builds
claudeArgsand how@anthropic-ai/claude-agent-sdk'squery()forwardsextraArgsto the spawned Claude CLI subprocess. The--mcp-configflag appears to be dropped or not consumed somewhere in that chain.From
src/modes/tag/index.ts:From
base-action/src/parse-sdk-options.ts:The code path is correct; the runtime outcome isn't.
Workaround (for anyone finding this)
We shipped a wrapper script instead. Claude writes
/tmp/claude-review.jsonvia theWritetool (path-scoped), and a separate workflow step runs a bash script that validates the JSON and posts viaPOST /repos/{owner}/{repo}/pulls/{n}/reviews. Happy to share the workflow as a gist if useful.Asks
--mcp-configfromextraArgsis supposed to reach the spawned Claude CLI when using@anthropic-ai/claude-agent-sdk'squery()?Happy to provide full run logs (multiple, with
show_full_output: trueenabled) on request. I've redacted nothing that's sensitive but didn't want to paste hundreds of kB inline.