{"record":{"id":"6cbecec05d12ff6d","repo":"siyuan-note/siyuan","slug":"oauth-authorization-failed-s","errorCode":null,"errorMessage":"OAuth authorization failed: %s","messagePattern":"OAuth authorization failed: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/mcp/client/oauth.go","lineNumber":386,"sourceCode":"\t}\n\toauthFlows.Lock()\n\toauthFlows.items[flowID] = flow\n\toauthFlows.Unlock()\n\tdefer removeOAuthFlow(flowID, flow)\n\tsetMCPRuntimeStateForContext(ctx, h.server.ID, \"authorizing\", 0, \"\", authorizationURL)\n\n\tvar callback oauthCallbackResult\n\ttimer := time.NewTimer(oauthAuthorizationTimeout)\n\tdefer timer.Stop()\n\tselect {\n\tcase callback = <-flow.Result:\n\tcase <-ctx.Done():\n\t\treturn ctx.Err()\n\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}","sourceCodeStart":368,"sourceCodeEnd":404,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/mcp/client/oauth.go#L368-L404","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Authorize surfaces the AS error string verbatim in the message.\nif err != nil && strings.Contains(err.Error(), \"OAuth authorization failed:\") {\n    // Extract the AS-supplied error code to drive UX (e.g. access_denied -> ask user to approve).\n    setMCPRuntimeStateForContext(ctx, serverID, \"authorization_required\", 0, err.Error(), \"\")\n}","preventionTips":["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)."],"tags":["oauth","mcp","callback","user-consent","authorization-error"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}