siyuan-note/siyuan · error
OAuth authorization failed: %s
Error message
OAuth authorization failed: %s
What it means
Returned at oauth.go:385-386 when the callback arrived in time but carried a non-empty Error field. The callback handler CompleteMCPOAuth forwards the OAuth error string from the redirect's error/error_description parameters; this surfaces an AS-side or user-side failure during consent.
Source
Thrown at kernel/mcp/client/oauth.go:386
}
oauthFlows.Lock()
oauthFlows.items[flowID] = flow
oauthFlows.Unlock()
defer removeOAuthFlow(flowID, flow)
setMCPRuntimeStateForContext(ctx, h.server.ID, "authorizing", 0, "", authorizationURL)
var callback oauthCallbackResult
timer := time.NewTimer(oauthAuthorizationTimeout)
defer timer.Stop()
select {
case callback = <-flow.Result:
case <-ctx.Done():
return ctx.Err()
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)
}View on GitHub (pinned to 251596fc0d)
Solutions
- Read the %s portion of the message — it contains the OAuth error code/description from the AS which names the exact reason.
- For access_denied, re-prompt the user to approve consent; for invalid_scope, reduce requested scopes or grant them server-side.
- Confirm the client registration's requested scopes match what the AS permits for the user.
Defensive patterns
Strategy: try-catch
Try / catch
// Authorize surfaces the AS error string verbatim in the message.
if err != nil && strings.Contains(err.Error(), "OAuth authorization failed:") {
// Extract the AS-supplied error code to drive UX (e.g. access_denied -> ask user to approve).
setMCPRuntimeStateForContext(ctx, serverID, "authorization_required", 0, err.Error(), "")
} Prevention
- Request only scopes the user is authorized to grant to avoid invalid_scope at consent.
- Pre-approve client consent administratively where possible to prevent user denial.
- Surface the AS error code to the user so they know whether to retry (access_denied) or reconfigure (invalid_scope).
When it happens
Trigger: The AS redirected to the callback with ?error=access_denied or ?error=invalid_scope (and optional error_description). Common: user clicked 'Deny' on the consent screen, or the AS rejected the requested scopes/client at the authorization step.
Common situations: User denied consent; AS policy blocked the client; requested scopes exceed what the user is allowed; AS misconfigured scope validation; admin revoked consent mid-flow.
Related errors
- OAuth authorization timed out
- OAuth state mismatch
- OAuth callback did not include an authorization code
- OAuth flow is missing or expired
- mcp oauth authorization required
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/6cbecec05d12ff6d.
Report an issue: GitHub.