{"record":{"id":"cc8dcaacb1435da4","repo":"BloopAI/vibe-kanban","slug":"google-token-exchange-failed-detail","errorCode":null,"errorMessage":"google token exchange failed: {detail}","messagePattern":"google token exchange failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/remote/src/auth/provider.rs","lineNumber":571,"sourceCode":"                        (!trimmed.is_empty()).then_some(trimmed.to_string())\n                    })\n                    .collect();\n\n                Ok(AuthorizationGrant {\n                    access_token: SecretString::new(access_token.into()),\n                    token_type,\n                    scopes,\n                    refresh_token: refresh_token.map(|v| SecretString::new(v.into())),\n                    expires_in: expires_in.map(Duration::seconds),\n                    id_token: id_token.map(|v| SecretString::new(v.into())),\n                })\n            }\n            GoogleTokenResponse::Error {\n                error,\n                error_description,\n            } => {\n                let detail = error_description.unwrap_or_else(|| error.clone());\n                anyhow::bail!(\"google token exchange failed: {detail}\")\n            }\n        }\n    }\n\n    async fn fetch_user(&self, access_token: &SecretString) -> Result<ProviderUser> {\n        let bearer = format!(\"Bearer {}\", access_token.expose_secret());\n\n        let profile: GoogleUser = self\n            .client\n            .get(\"https://openidconnect.googleapis.com/v1/userinfo\")\n            .header(\"Authorization\", bearer)\n            .send()\n            .await?\n            .error_for_status()?\n            .json()\n            .await?;\n\n        let login = profile.email.clone();","sourceCodeStart":553,"sourceCodeEnd":589,"githubUrl":"https://github.com/BloopAI/vibe-kanban/blob/4deb7eca8f381f7cbc1f9d15515a9ab8f8009053/crates/remote/src/auth/provider.rs#L553-L589","documentation":"During the Google OAuth code-for-token exchange, Google's token endpoint returned an error variant (e.g. invalid_grant, redirect_uri_mismatch) instead of tokens. The provider wraps Google's error code or description into this bail message.","triggerScenarios":"exchange_code() on the Google provider receives GoogleTokenResponse::Error — typically because the code is expired/used, or redirect_uri/client credentials don't match the original request.","commonSituations":"Authorization code expired (Google codes are short-lived, ~10 min) or already redeemed; redirect URI not whitelisted in Google Cloud Console; wrong client secret between environments (staging vs prod); refresh-token flow attempted with the wrong grant type.","solutions":["Restart the OAuth flow to get a fresh authorization code and exchange it immediately","Ensure the redirect_uri exactly matches one registered in Google Cloud Console","Verify client_id/client_secret for the correct Google OAuth client/environment","If using offline access, request the correct scopes/access_type and use refresh_token grant appropriately"],"exampleFix":"// before\n// token request redirect_uri = \"http://localhost:3000/callback\"\n// authorize request redirect_uri = \"https://app.example.com/callback\" -> invalid_grant / redirect_uri_mismatch\n// after\n// use the identical redirect_uri in both authorize and token requests","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"match provider.exchange_code(&code).await { Err(e) if e.to_string().starts_with(\"google token exchange failed\") => restart_oauth_flow(), other => other? }","preventionTips":["Exchange Google codes promptly (they expire in minutes)","Register and use the exact redirect_uri in Google Cloud Console","Verify client secret per environment","Never reuse an authorization code; start a new flow for retries"],"tags":["oauth","google","token-exchange","http"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"4deb7eca8f381f7cbc1f9d15515a9ab8f8009053","analyzedAt":"2026-08-29T09:24:13.446Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}