CherryHQ/cherry-studio · error · Error

OAuth authentication failed: ${oauthError instanceof Error ?

Error message

OAuth authentication failed: ${oauthError instanceof Error ? oauthError.message : String(oauthError)}

What it means

Thrown when the OAuth authentication flow fails during `transport.finishAuth(authCode)` or the subsequent reconnection attempt (`client.connect(newTransport)`). The original error is logged via getServerLogger before being wrapped. This is a user-facing auth failure, not a transport-level protocol mismatch (those are handled by isTransportFallbackError separately).

Source

Thrown at src/main/ai/mcp/McpRuntimeService.ts:768

          try {
            // Wait for the authorization code
            const authCode = await callbackServer.waitForAuthCode()
            getServerLogger(server).debug(`Received auth code`)

            // Complete the OAuth flow
            await transport.finishAuth(authCode)

            getServerLogger(server).debug(`OAuth flow completed`)

            const newTransport = await initTransport(typeOverride)
            // Try to connect again
            await client.connect(newTransport)

            getServerLogger(server).debug(`Successfully authenticated`)
          } catch (oauthError) {
            getServerLogger(server).error(`OAuth authentication failed`, oauthError as Error)
            throw new Error(
              `OAuth authentication failed: ${oauthError instanceof Error ? oauthError.message : String(oauthError)}`
            )
          } finally {
            // Clear the timeout and close the callback server
            clearTimeout(timeoutId)
            void callbackServer.close()
          }
        }

        try {
          // Bound the MCP `initialize` request so a non-responsive server fails fast via the
          // SDK's own abort path instead of hanging. Use a 180s floor (activation runs once,
          // generous headroom is cheap) while still honoring larger `server.timeout` values
          // that the user explicitly configured. transport.start() latency remains bounded
          // by the underlying fetch / child_process, matching v1.8.4 behavior.
          const connectOptions: RequestOptions = {
            timeout: Math.max((server.timeout ?? 0) * 1000, MCP_CONNECT_TIMEOUT_FLOOR_MS)
          }

View on GitHub (pinned to 726446b54c)

Solutions

  1. Retry the OAuth flow from scratch — the callback server and timeout are cleaned up automatically
  2. Check the detailed server logs for the original oauthError message (logged before this throw)
  3. Verify the OAuth provider's metadata endpoint is accessible and the redirect URI is correctly registered
  4. If PKCE mismatch, clear the OAuth storage for this server and retry (storage.clear())
  5. Ensure the system clock is accurate — token validation is time-sensitive
Defensive patterns

Strategy: retry

Try / catch

const MAX_OAUTH_RETRIES = 2
for (let attempt = 0; attempt <= MAX_OAUTH_RETRIES; attempt++) {
  try {
    await runtime.getOrCreateClient(server)
    break
  } catch (e) {
    if (e instanceof Error && e.message.startsWith('OAuth authentication failed') && attempt < MAX_OAUTH_RETRIES) {
      // Clear stale OAuth state and retry the full flow
      await oauthStorage.clear()
      continue
    }
    throw e
  }
}

Prevention

When it happens

Trigger: The OAuth callback delivers an auth code, but `finishAuth` rejects it (expired, already used, or PKCE mismatch), or the reconnection with the new transport fails due to network errors, invalid tokens, or server-side OAuth endpoint issues.

Common situations: Auth code expired before the user completed the consent flow; PKCE code_verifier doesn't match (storage issue); the OAuth server's token endpoint is down; redirect URI mismatch; the OAuth provider changed its API; network firewall blocks the token exchange.

Understand the failure class

Related errors


AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12). Data as JSON: /api/errors/43fd830545ceaee4. Report an issue: GitHub.