siyuan-note/siyuan · error
exchange OAuth authorization code: %w
Error message
exchange OAuth authorization code: %w
What it means
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.
Source
Thrown at kernel/mcp/client/oauth.go:400
case <-timer.C:
return fmt.Errorf("OAuth authorization timed out")
}
if callback.Error != "" {
return fmt.Errorf("OAuth authorization failed: %s", callback.Error)
}
if callback.State != state {
return fmt.Errorf("OAuth state mismatch")
}
if callback.Code == "" {
return fmt.Errorf("OAuth callback did not include an authorization code")
}
exchangeCtx := context.WithValue(ctx, oauth2.HTTPClient, h.client)
token, err := config.Exchange(exchangeCtx, callback.Code,
oauth2.VerifierOption(verifier),
oauth2.SetAuthURLParam("resource", prm.Resource))
if err != nil {
return fmt.Errorf("exchange OAuth authorization code: %w", err)
}
if token.TokenType != "" && !strings.EqualFold(token.TokenType, "Bearer") {
return fmt.Errorf("OAuth token endpoint returned unsupported token type %q", token.TokenType)
}
credential = registrationCredential
credential.TokenAuthMethod = authMethod
credential.AccessToken = token.AccessToken
credential.RefreshToken = token.RefreshToken
credential.TokenType = token.TokenType
credential.Expiry = token.Expiry
credential.Scopes = scopes
credential.Rejected = false
if err = putOAuthCredential(credential); err != nil {
return fmt.Errorf("save OAuth credentials: %w", err)
}
h.sourceMu.Lock()
h.source = &storedOAuthTokenSource{credential: credential, client: h.client}
h.sourceMu.Unlock()View on GitHub (pinned to 251596fc0d)
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.
Defensive patterns
Strategy: retry
Try / catch
token, exchangeErr := config.Exchange(exchangeCtx, callback.Code, oauth2.VerifierOption(verifier), ...)
if exchangeErr != nil {
// invalid_grant due to a one-off (clock skew, transient) may justify a full re-Authorize,
// never a replay of the same code (codes are single-use).
if strings.Contains(exchangeErr.Error(), "invalid_grant") {
// prompt user to re-authorize from scratch
}
return fmt.Errorf("exchange OAuth authorization code: %w", exchangeErr)
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- OAuth authorization server does not support PKCE S256
- OAuth token endpoint returned unsupported token type %q
- mcp oauth authorization required
- parse OAuth challenge: %w
- server returned %s without an OAuth Bearer challenge
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/38a8024569b58e03.
Report an issue: GitHub.