{"record":{"id":"38a8024569b58e03","repo":"siyuan-note/siyuan","slug":"exchange-oauth-authorization-code-w","errorCode":null,"errorMessage":"exchange OAuth authorization code: %w","messagePattern":"exchange OAuth authorization code: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/mcp/client/oauth.go","lineNumber":400,"sourceCode":"\tcase <-timer.C:\n\t\treturn fmt.Errorf(\"OAuth authorization timed out\")\n\t}\n\tif callback.Error != \"\" {\n\t\treturn fmt.Errorf(\"OAuth authorization failed: %s\", callback.Error)\n\t}\n\tif callback.State != state {\n\t\treturn fmt.Errorf(\"OAuth state mismatch\")\n\t}\n\tif callback.Code == \"\" {\n\t\treturn fmt.Errorf(\"OAuth callback did not include an authorization code\")\n\t}\n\n\texchangeCtx := context.WithValue(ctx, oauth2.HTTPClient, h.client)\n\ttoken, err := config.Exchange(exchangeCtx, callback.Code,\n\t\toauth2.VerifierOption(verifier),\n\t\toauth2.SetAuthURLParam(\"resource\", prm.Resource))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"exchange OAuth authorization code: %w\", err)\n\t}\n\tif token.TokenType != \"\" && !strings.EqualFold(token.TokenType, \"Bearer\") {\n\t\treturn fmt.Errorf(\"OAuth token endpoint returned unsupported token type %q\", token.TokenType)\n\t}\n\tcredential = registrationCredential\n\tcredential.TokenAuthMethod = authMethod\n\tcredential.AccessToken = token.AccessToken\n\tcredential.RefreshToken = token.RefreshToken\n\tcredential.TokenType = token.TokenType\n\tcredential.Expiry = token.Expiry\n\tcredential.Scopes = scopes\n\tcredential.Rejected = false\n\tif err = putOAuthCredential(credential); err != nil {\n\t\treturn fmt.Errorf(\"save OAuth credentials: %w\", err)\n\t}\n\th.sourceMu.Lock()\n\th.source = &storedOAuthTokenSource{credential: credential, client: h.client}\n\th.sourceMu.Unlock()","sourceCodeStart":382,"sourceCodeEnd":418,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/mcp/client/oauth.go#L382-L418","documentation":"Wrapped error at oauth.go:399-400 from oauth2.Config.Exchange when swapping the authorization code for tokens fails. Exchange POSTs to asm.TokenEndpoint with the code, PKCE verifier, redirect_uri, client credentials, and the resource parameter; any failure (4xx, network, PKCE mismatch) is wrapped here.","triggerScenarios":"Token endpoint returns 400 invalid_grant (code expired, already used, or PKCE verifier mismatch), 401 (wrong client auth), or a network error; or the response body fails JSON parsing / lacks access_token.","commonSituations":"Code replayed (each code is single-use); PKCE verifier corrupted in memory; clock skew causing token endpoint to reject; client_secret mismatch; token_endpoint_auth_method mismatch between registration and exchange; network interruption to token endpoint.","solutions":["Read the wrapped error for the OAuth2 error code (invalid_grant, invalid_client, etc.) — it pinpoints the cause.","Ensure the authorization code is used exactly once immediately after receipt (no retries with the same code).","Verify the PKCE verifier/state are from the same in-memory flow and that clock skew between client and AS is within tolerance.","Confirm client credentials sent to the token endpoint match the registered client_id/secret and the agreed token_endpoint_auth_method."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"token, exchangeErr := config.Exchange(exchangeCtx, callback.Code, oauth2.VerifierOption(verifier), ...)\nif exchangeErr != nil {\n    // invalid_grant due to a one-off (clock skew, transient) may justify a full re-Authorize,\n    // never a replay of the same code (codes are single-use).\n    if strings.Contains(exchangeErr.Error(), \"invalid_grant\") {\n        // prompt user to re-authorize from scratch\n    }\n    return fmt.Errorf(\"exchange OAuth authorization code: %w\", exchangeErr)\n}","preventionTips":["Use each authorization code exactly once; never retry Exchange with the same code.","Keep client clock synchronized (NTP) to avoid token-endpoint rejection.","Ensure the PKCE verifier and code_challenge come from the same in-memory flow."],"tags":["oauth","mcp","token-exchange","pkce","token-endpoint","invalid-grant"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}