Skip to content

fix: cap OAuth reconnect retries instead of looping forever - #3

Open
janpio wants to merge 1 commit into
mainfrom
claude/oauth-retry-cap
Open

janpio wants to merge 1 commit into
mainfrom
claude/oauth-retry-cap

Conversation

@janpio

@janpio janpio commented Jul 24, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes an infinite reconnect loop: when a connection fails auth and the stored token is accepted by the OAuth flow but rejected by the server (e.g. the authenticated account isn't authorized for that MCP server), the Inspector retries forever — spinning up a new transport and re-running OAuth discovery on every iteration, flooding the server and burying the real 401.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)

Changes Made

In useConnection.connect(), an auth failure calls handleAuthError(), which re-runs auth(). auth() returns AUTHORIZED whenever a token is already stored, so shouldRetry is true and connect() recurses — with no cap on retryCount. If the server keeps returning 401 for that token, the recursion never terminates.

  • Cap the re-auth retry at one attempt (MAX_CONNECT_AUTH_RETRIES = 1). One retry still covers the legitimate case (we just obtained a token); beyond that, a persistently-rejecting server stops instead of looping.
  • On exhaustion, set the error connection state and show a toast pointing at the likely cause (account may lack access) rather than silently spinning.
  • Added a unit test asserting connect() attempts exactly twice (initial + one retry) and lands in the error state when the server keeps 401ing.

Happy path and the single-retry recovery path are unchanged.

Related Issues

Surfaced while testing OAuth against a deployment where the logged-in user wasn't authorized: the server returned a legitimate 401 on every attempt and the client looped indefinitely, making the real error impossible to see.

Testing

  • Tested in UI mode
  • Tested with STDIO transport
  • Tested with SSE transport
  • Tested with Streamable HTTP transport
  • Added/updated automated tests
  • Manual testing performed

Test Results and/or Instructions

To reproduce the original loop: connect (Streamable HTTP, Via Proxy) with OAuth to a server that will 401 the obtained token (e.g. authenticate as a user without access). Before: an endless stream of identical /mcp requests and repeating New StreamableHttp connection request … Error … 401 in the proxy console. After: two attempts, then the connection goes to error with a toast.

New unit test (useConnection → "Auth retry cap") covers it. Full client suite (540) + lint + build pass.

Checklist

  • Code follows the style guidelines (ran npm run prettier-fix)
  • Self-review completed
  • Code is commented where necessary
  • Documentation updated (README) — n/a, internal behavior fix

Breaking Changes

None. Only changes behavior in the previously-infinite failure case.

Additional Context

Independent of the "apply custom headers to OAuth requests" work (PR #1) and the dev-launcher --strictPort fix (PR #2) — this touches only the retry recursion in useConnection. Pre-existing behavior, not introduced by either of those.

🤖 Generated with Claude Code


Generated by Claude Code

When connect() fails with an auth error, handleAuthError re-runs auth() and, if
it reports success (which it does whenever a token is already stored), connect()
retries. If the server keeps returning 401 for that token — e.g. the
authenticated account isn't authorized for the server — this recursion never
terminates: it spins up a new transport and re-runs OAuth discovery on every
iteration, hammering the server and burying the real 401 response under a flood
of identical requests.

Cap the re-auth retry at one attempt. One retry still covers the legitimate
case (we just obtained a token), but a persistently-rejecting server now stops
after the second failure, sets the error state, and surfaces a message instead
of looping.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012FAFCf8AzpqyNF1oKZNGzh
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants