[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
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:
- A scope error is surfaced to the user as a login rejection, sending them into a retry loop that cannot work.
- A rejected claude.ai login produces zero user-visible signal in the VS Code extension when
claudeCode.disableLoginPrompt: true— connectors silently vanish from/mcpwith no banner, badge, or error. - 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 listwithCLAUDECODEandCLAUDE_CODE_SESSION_IDunset listed every connector as Connected throughout, so #69417's mechanism does not apply. claude mcp listreporting "connected" is not evidence tools are live in a session. It reported 11 of 13 connected while zeromcp__claude_ai_*tools reached the session.- No stale
CLAUDE_CODE_OAUTH_TOKENwas pinned in shell profiles, Claude settings files,launchctl, or VS Code'sterminal.integrated.env.*. - In-app
/bugsubmission 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
4 Comments
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:
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
/mcpand exposes nomcp__claude_ai_*tools, while the CLI resolves them fine on the same machine. The keychain service proliferation (105 orphanedClaude Code-credentials-<hash>services, 1-3/day) is unchanged and still reproduces.Title and body updated to match. Apologies for the noise.
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: truein 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:/loginfixed it./mcpnow 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:
claude mcp listlisted every connector as Connected the whole time.Leaving this open for the two defects that stand independently of my setting:
disableLoginPromptis set. Suppressing the prompt is intended; suppressing every trace of the rejection is what made this undiagnosable for ~7 weeks.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.
Correcting the Keychain data in my earlier revisions. Two measurement errors, both from a bad
grep -A30that picked upcdatlines 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
/loginseveral 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-credentialsaccount-login service updates in place correctly.Body revised to carry only directly measured values. Also added the
oauth_scope_insufficientfinding, which is the most actionable item here and was missing from earlier revisions.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-credentialsKeychain service'smdatadvanced — and the resulting token still returns: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:
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
/loginrefreshes 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-controlreliably 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.