[BUG] `oauth_scope_insufficient` is surfaced as "Claude.ai login was rejected", driving an unfixable /login loop; the same failure is fully silent in the VS Code extension when `disableLoginPrompt` is set

Status Open
Reported on v2.1.260
Maintainer reply None cached
Activity 4 comments · opened Sep 4, 2026
Revised 2026-09-04. Earlier revisions of this issue contained two measurement errors in the Keychain data (wrong time span, and a claim that each authorization mints a service). Both are corrected below; see the comments for what changed. Only directly measured values remain.

Summary

Three findings from diagnosing why claude.ai connectors were unavailable in the VS Code extension:

  1. A scope error is surfaced to the user as a login rejection, sending them into a retry loop that cannot work.
  2. A rejected claude.ai login produces zero user-visible signal in the VS Code extension when claudeCode.disableLoginPrompt: true — connectors silently vanish from /mcp with no banner, badge, or error.
  3. 106 orphaned top-level Keychain services named Claude Code-credentials-<8-hex-hash> accumulating at ~1 per day of use since 2026-04-24, each written once and never read again.

Finding 1: scope error reported as a login rejection

Claude Code displays:

Remote Control disconnected - Claude.ai login was rejected - run /login, then /remote-control

The underlying API response is not a rejected login:

HTTP 401
{"error":{"message":"OAuth token lacks a scope this endpoint accepts",
          "reason":"oauth_scope_insufficient","type":"authentication_error"}}

The token authenticates. It lacks a scope for /v1/code/*. /login performs a token refresh that reuses the existing scope set, so following the instruction produces a valid token missing the same scope, indefinitely. The message points the user at the one action that cannot resolve it.

Account is organizationType: claude_max, organizationRole: admin, so this is not a plan gate.

Suggested: surface oauth_scope_insufficient distinctly and direct the user to whatever grants the scope (/remote-control, or a full re-consent) rather than to /login.

Finding 2: silent auth failure in the extension

claudeCode.disableLoginPrompt is documented as "When true, never prompt for login/authentication in the extension. Used when authentication is handled externally."

Suppressing the prompt is intended. Suppressing every trace that authentication failed is not. With it set, the extension's /mcp renders normally and simply omits the claude.ai connectors group:

User MCPs (~/.claude.json)
  blotato                       connected  35 tools
  blotato_nick                  connected  35 tools
Built-in MCPs (always available)
  plugin:lovable:lovable        connected  40 tools
  plugin:playwright:playwright  connected  24 tools

No connectors group, no mcp__claude_ai_* tools, no indication anything is wrong. A terminal session states the cause on line one. After /login, /mcp shows all 13 servers (Gmail 29 tools, Era Context 51, Lovable 40, Slack 13, Drive 11, Calendar 9, Fathom 9).

Setting disableLoginPrompt back to its default made the failure immediately visible, confirming the setting was what hid it.

Suggested: a non-modal indication on the /mcp panel when connectors are absent due to an auth failure, shown regardless of disableLoginPrompt.

Finding 3: Keychain service accumulation

Measured by querying each service directly (security find-generic-password -s <name>, metadata only, no secret material read):

services:  106 distinct  (1 bare + 105 hash-suffixed)
first:     2026-04-24
last:      2026-09-04
span:      134 days, of which 100 have an entry and 34 have none
rate:      ~1 per day of use

The bare Claude Code-credentials service (created 2026-07-17) is the account login and updates in place correctly. The suffixed siblings do not: a sampled entry was modified exactly 1 second after creation and never touched again.

Correcting an earlier claim in this issue: these are not minted per authorization attempt. On 2026-09-04 the user ran /login several times and exactly one service exists for that date. The rate tracks days of use, which is more consistent with a daily rotation that writes to a new service name than with per-auth registration.

Each holds OAuth material for connected Google and Slack accounts. 34 gap days align with non-use.

This differs in shape from #59460, where orphans live inside one keychain item under mcpOAuth.<serverName>|<urlHash>. Here they are separate top-level services.

Notes for triage

  • Not a persistence failure. A cold-process claude mcp list with CLAUDECODE and CLAUDE_CODE_SESSION_ID unset listed every connector as Connected throughout, so #69417's mechanism does not apply.
  • claude mcp list reporting "connected" is not evidence tools are live in a session. It reported 11 of 13 connected while zero mcp__claude_ai_* tools reached the session.
  • No stale CLAUDE_CODE_OAUTH_TOKEN was pinned in shell profiles, Claude settings files, launchctl, or VS Code's terminal.integrated.env.*.
  • In-app /bug submission fails with HTTP 403, which is why this is filed here.

Ruled out during diagnosis

| Setting | Location checked | Value |
|---|---|---|
| disableClaudeAiConnectors | settings.json, settings.local.json, .claude.json | not set |
| deniedMcpServers | all three | not set |
| disabledMcpServers / enabledMcpServers | global + all project scopes | not set |
| ENABLE_CLAUDEAI_MCP_SERVERS | environment | unset |
| CLAUDE_CONFIG_DIR | environment | unset |

Auth mode is claude.ai OAuth. ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, CLAUDE_CODE_USE_BEDROCK, CLAUDE_CODE_USE_VERTEX, ANTHROPIC_BASE_URL all unset. Single native install.

Environment

  • Claude Code 2.1.260, native install (~/.local/bin/claude). 2.1.258 installed 09-01, 2.1.259 on 09-03, 2.1.260 on 09-03.
  • VS Code extension anthropic.claude-code-2.1.260-darwin-arm64 (installed 09-04; 2.1.259 on 09-03)
  • macOS, Darwin 25.3.0, arm64
  • Credential storage: Keychain
  • remoteControlAtStartup: true, which is why the failure announces itself every session

Related: #69417, #76150, #59460, #80422

View original on GitHub ↗

4 Comments

rbrookec-cloud · 12 hours ago

Correction to the original report.

As first filed, this claimed connector credentials are never persisted locally, following #69417. That was wrong and the body has been rewritten.

I then ran the cold-process test:

env -u CLAUDECODE -u CLAUDE_CODE_SESSION_ID claude mcp list

Output was identical to the warm invocation — same 13 servers, 11 connected including Gmail/Drive/Calendar/Slack/Fathom, same 2 needing auth. Per #69417 a cold invocation should have listed zero connectors. It did not, so credentials are persisted and are readable from a fresh process, and the #69417 mechanism does not apply here.

What actually reproduces: the VS Code extension session omits the claude.ai connectors group from /mcp and exposes no mcp__claude_ai_* tools, while the CLI resolves them fine on the same machine. The keychain service proliferation (105 orphaned Claude Code-credentials-<hash> services, 1-3/day) is unchanged and still reproduces.

Title and body updated to match. Apologies for the noise.

rbrookec-cloud · 12 hours ago

Resolved on my end, and the report has been rewritten around what was actually wrong.

Cause: the claude.ai login was being rejected, while claudeCode.disableLoginPrompt: true in VS Code user settings told the extension never to surface a login prompt. The extension therefore showed nothing at all — no banner, no badge, connectors simply absent from /mcp. A terminal session showed the cause on the first line:

Remote Control disconnected - Claude.ai login was rejected - run /login, then /remote-control

/login fixed it. /mcp now lists 13 servers with the claude.ai group present (Gmail 29 tools, Era Context 51, Lovable 40, Slack 13, Drive 11, Calendar 9, Fathom 9).

Both of my earlier theories were wrong:

  • Not a persistence failure (#69417's mechanism). A cold-process claude mcp list listed every connector as Connected the whole time.
  • Not the extension failing to load connectors it could otherwise see. It was never authenticated to fetch them.

Leaving this open for the two defects that stand independently of my setting:

  1. A rejected login is completely invisible in the extension when disableLoginPrompt is set. Suppressing the prompt is intended; suppressing every trace of the rejection is what made this undiagnosable for ~7 weeks.
  2. 105 orphaned top-level Claude Code-credentials-<hash> Keychain services, one per authorization attempt, each holding OAuth material for connected Google and Slack accounts.

Happy to close if maintainers consider both working as intended.

rbrookec-cloud · 11 hours ago

Correcting the Keychain data in my earlier revisions. Two measurement errors, both from a bad grep -A30 that picked up cdat lines belonging to neighbouring, unrelated Keychain items.

Re-measured by querying each service by name:

| | previously stated | actual |
|---|---|---|
| span | ~7 weeks | 134 days, from 2026-04-24 |
| rate | 1-3 per day | ~1 per day of use (100 of 134 days have an entry) |
| trigger | one per authorization attempt | not per attempt |

The last row matters most. On 2026-09-04 I ran /login several times while diagnosing, and exactly one service exists for that date. So these are not minted per authorization; the rate tracks days of use, which points at a daily rotation writing to a new service name rather than per-auth registration. The retry-loop-causes-orphans theory in my earlier text was wrong.

What survives unchanged: 106 distinct services, 105 of them hash-suffixed, each written once and modified ~1 second later and never read again, each holding OAuth material for connected Google and Slack accounts. The bare Claude Code-credentials account-login service updates in place correctly.

Body revised to carry only directly measured values. Also added the oauth_scope_insufficient finding, which is the most actionable item here and was missing from earlier revisions.

rbrookec-cloud · 7 hours ago

Negative result on the version-regression theory, and a narrower hypothesis.

Rolled the CLI back to 2.1.259 (symlink repoint, restarted VS Code) and re-tested. The credential re-authenticated — the bare Claude Code-credentials Keychain service's mdat advanced — and the resulting token still returns:

HTTP 401
{"error":{"message":"OAuth token lacks a scope this endpoint accepts",
          "reason":"oauth_scope_insufficient","type":"authentication_error"}}

Two different client versions each obtained a token missing the scope, so this is not a client regression and the server is issuing it that way. Restored to 2.1.260.

Narrower hypothesis. A prior changelog entry reads:

Fixed dismissing the Remote Control consent prompt (Esc, or n at claude remote-control) counting as consent, so the next request connected without asking

If a dismissal was ever recorded as consent on this account, there would be a consent record that never granted the scope — which presents exactly as a token that authenticates but lacks it, and which no number of /login refreshes can repair. That would also explain why the user-facing message ("Claude.ai login was rejected") is the least useful thing to show: the fix is re-consent, not re-login.

Worth checking whether a stale/incomplete Remote Control consent record can persist server-side and, if so, whether /remote-control reliably re-prompts rather than trusting the stored record.

Environment unchanged: Claude Code 2.1.260, VS Code extension 2.1.260, macOS arm64, organizationType: claude_max, remoteControlAtStartup: true.