[BUG] CCR proxy returns -32003 needs_approval and the retroactive approval card cannot succeed — root cause analysis from the shipped client (2.1.220)

Status Open
Reported on v2.1.220
Maintainer reply None cached
Activity 2 comments · opened Jul 26, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

MCP tool calls fail with MCP error -32003: MCP tool call requires approval after the user has already approved them — twice.

-32003 is not raised by the client's permission layer. It is returned by the CCR MCP proxy (api.anthropic.com/v2/ccr-sessions/{session}/mcp) as needs_approval. The client answers it by surfacing a second, retroactive approval card and retrying the identical arguments. In the failing case the retry is refused again, so the user has approved twice and still sees -32003.

That card is constructed with suppressAlwaysAllowRule: true hardcoded, so no "Always allow" is ever offered and permissions.allow entries in settings.json cannot affect this path. Several open issues advise users to fix their allowlist; the allowlist provably cannot help here.

What I see inside a single session:

| Server registration | Result | Approval dialogs |
| --- | --- | --- |
| Friendly name (e.g. Claude_Code_Remote) early in the session | works | one |
| Same server, UUID registration, after a mid-session reconnect | MCP error -32003 | two |
| Friendly name after that reconnect | No such tool available | zero |
| A different connector (Gmail, Drive) under its UUID, same moment | works | one |

The last row matters: the UUID naming itself is harmless. Something else distinguishes this server — see Additional information.

Existing reports of the same fault: #61015, #61027, #61044, #61097, #61143, #79711, #79983.

What Should Happen?

Either the retroactive approval should actually satisfy the proxy on retry, or — better — a connector the user has already authorised should not require per-call approval at all.

Today the user approves twice and the call still fails, with an error message that names approval as the problem when approval has already been given.

Specifically:

  1. Once the retroactive approval card is accepted, the retry should be accepted by the proxy rather than returning needs_approval again.
  2. The friendly-name registration of a statsig-origin server should be restored after an MCP reconnect, instead of leaving only the UUID-keyed duplicate that cannot be approved.
  3. If per-call approval genuinely is required by design, the error should say so, and permissions.allow should either work or be documented as inapplicable — right now users are repeatedly advised to fix an allowlist that cannot reach this code path.

For unattended Routines this is the difference between annoying and fatal: there is nobody present to tap the retroactive card, so an affected connector cannot be used at all.

Error Messages/Logs

The error returned to the model, verbatim:


MCP error -32003: MCP tool call requires approval


Recognised by the client as `error.code === -32003` together with `"args_sha256" in error.data`.

Log line in the shipped bundle (2.1.220):


Tool '${tool}' returned -32003 needs_approval (tool_name=…) — surfacing retroactive approval card


Telemetry: `tengu_mcp_proxy_needs_approval_retry`
Metric: `mcp_ccr_needs_approval`, with outcomes `retry_failed`, `no_prompt_surface`, `arm_not_fired`, `edited_input`, `denied`

The permission card the client raises:


{
  behavior: "ask",
  suppressAlwaysAllowRule: true,
  message: `The ${fullyQualifiedName} connector requires approval for this call.`,
  decisionReason: { type: "other", reason: "This connector call requires your approval to proceed." }
}


On `allow` it retries the **identical** arguments; any edited input aborts with `ccr_proxy_needs_approval_edited_input`. The retry is armed once per call.

For the same server under its friendly name after the reconnect, no dialog appears at all and the call returns:


No such tool available

Steps to Reproduce

  1. Start a Claude Code on the web session with connectors attached.
  2. Within the first minutes, call a tool on the harness-injected remote/meta MCP server (e.g. a routine tool such as list_triggers). Approve the single dialog — it succeeds.
  3. Keep working. At some point the MCP servers reconnect on their own — no restart, no config change, no user action.
  4. Call the same tool again:
  • under the friendly name → No such tool available, no dialog at all
  • under the connector UUID → two dialogs, both approved, then MCP error -32003
  1. In the same session, call a Gmail or Drive tool under its UUID — it succeeds with one dialog, which shows the UUID naming itself is not the fault.

Measured in one session: last success at +4m36s from session start, first failure at +16m, with the reconnect in between. After that the affected server stays unusable for the rest of the session; only starting a new session restores it.

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.220

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

Other

Additional Information

Why one server dies and the others survive the same reconnect

The client writes /tmp/mcp-config-{session_id}.json. In an affected session it contains:

| Server key | Proxied mcp_url | X-MCP-Server-Origin |
| --- | --- | --- |
| bf7c680d-… (routine/meta server) | https://api.anthropic.com/v1/code/mcp/meta | statsig |
| 2de66761-… (Gmail) | https://gmailmcp.googleapis.com/mcp/v1 | custom |
| github | — (its own path /ccr-sessions/{id}/github/mcp) | — |

  1. Every proxied connector is keyed by connector UUID; only github gets a literal name. That is exactly why, after a reconnect, connectors reappear as mcp__<uuid>__* while GitHub keeps mcp__github__*. The "servers renamed themselves" effect several issues mention is just this file.
  2. The friendly-named registration is not in that file at all. The working registration comes from session bootstrap; what survives a reconnect is the UUID-keyed, proxy-routed duplicate of the same backend — i.e. the one that must pass needs_approval.
  3. The meta/routine server is injected with X-MCP-Server-Origin: statsig, not as a user connector. That is the clearest difference from the connectors that keep working.

Impact

  • On web and mobile there is no terminal to fall back to, so once this hits, the affected server is unusable for the rest of the session.
  • For Routines (unattended runs) it is fatal rather than annoying: there is nobody to tap the retroactive card, so an affected connector simply cannot be used. I believe that is what #61015 / #61027 / #61044 are describing.
  • permissions.allow in settings.json cannot help, for the hardcoded reason above. Several issues contain advice to fix the allowlist; it is a dead end and worth correcting in those threads.

Questions that would confirm or refute this

  1. Under what conditions does the proxy return needs_approval for a connector the user has already authorised — is it keyed to the tool's _meta["anthropic/requiresUserInteraction"]?
  2. On the retry, what is args_sha256 compared against, and does a change of server registration (bootstrap name → UUID key) invalidate the match?
  3. Is the friendly-name registration of a statsig-origin server expected to be restored after a reconnect? Right now it is not, and the surviving duplicate is the one that cannot be approved.

Related

#61015 (closed, "should be addressed", no cause or version given), #61027, #61143, #61097, #61044 (open — "no approval UI shown, reconnect does not resolve"), #79711, #79983. I think this analysis explains all of them; happy to have it merged into whichever is the canonical thread.

Account context: personal claude.ai subscription, no organization tool policies. Affected server: the harness-injected remote/meta MCP server (routine tools).

---
_Generated by Claude Code_

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗