{"record":{"id":"ea3435a43798c1b1","repo":"Hmbown/CodeWhale","slug":"oauth-provider-did-not-return-credentials","errorCode":null,"errorMessage":"OAuth provider did not return credentials","messagePattern":"OAuth provider did not return credentials","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/mcp/oauth.rs","lineNumber":870,"sourceCode":"                })?\n                .context(\"OAuth callback was cancelled\")?;\n            let OauthCallbackResult { code, state } = match callback {\n                CallbackResult::Success(callback) => callback,\n                CallbackResult::Error(error) => return Err(anyhow!(error)),\n            };\n\n            self.oauth_state\n                .handle_callback(&code, &state)\n                .await\n                .context(\"handling MCP OAuth callback\")?;\n\n            let (client_id, credentials) = self\n                .oauth_state\n                .get_credentials()\n                .await\n                .context(\"reading MCP OAuth credentials\")?;\n            let credentials =\n                credentials.ok_or_else(|| anyhow!(\"OAuth provider did not return credentials\"))?;\n            let stored = StoredMcpOAuthTokens {\n                server_name: self.server_name.clone(),\n                url: self.server_url.clone(),\n                client_id,\n                expires_at: compute_expires_at_millis(&credentials),\n                token_response: WrappedOAuthTokenResponse(credentials),\n            };\n            save_oauth_tokens(&stored)\n        }\n        .await;\n\n        drop(self.guard);\n        result\n    }\n}\n\nasync fn start_authorization(\n    server_url: &str,","sourceCodeStart":852,"sourceCodeEnd":888,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/tui/src/mcp/oauth.rs#L852-L888","documentation":"Thrown after an MCP OAuth callback completes but no token credentials are available. The code first calls handle_callback() (authorization-code exchange), then get_credentials(); the latter returns Option and None means the flow finished its redirect yet never persisted a token response. It signals a provider-side anomaly (token exchange skipped or failed silently) or flow state consumed/cleared between callback and read.","triggerScenarios":"Completing the localhost OAuth redirect for an MCP server (handle_callback succeeds) while get_credentials() returns None: a provider that redirects with a code but stores no tokens, or two concurrent callbacks where the first consumed the flow's credentials.","commonSituations":"MCP servers whose OAuth implementation performs the redirect but not a usable token exchange; a stale browser tab finishing an old authorize request after a new login attempt started; providers with unusual PKCE or token-endpoint behavior.","solutions":["Restart the MCP OAuth login from the beginning to rebuild the flow state","Verify the server's OAuth metadata advertises a working token endpoint that returns an access_token on exchange","Ensure only one browser/tab completes the callback for a single login attempt","Inspect earlier log lines for a token-exchange failure inside handle_callback that preceded this invariant break"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"Match on the message after the callback step; on this error restart the entire authorize flow (not just get_credentials) and cap attempts:\n```rust\nmatch complete_mcp_oauth_callback().await {\n    Err(e) if e.to_string().contains(\"OAuth provider did not return credentials\") => {\n        restart_authorize_flow().await? // rebuild flow state; max 2 attempts\n    }\n    other => other?,\n}\n```","preventionTips":["Complete only one OAuth login attempt per flow; close stale authorize tabs before retrying","Verify the provider's token endpoint with a standalone OAuth client before wiring it into an MCP server"],"tags":["mcp","oauth","authentication","credentials"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}