{"record":{"id":"662c77e0960ff2cc","repo":"siyuan-note/siyuan","slug":"oauth-callback-did-not-include-an-authorization-co","errorCode":null,"errorMessage":"OAuth callback did not include an authorization code","messagePattern":"OAuth callback did not include an authorization code","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/mcp/client/oauth.go","lineNumber":392,"sourceCode":"\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}\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","sourceCodeStart":374,"sourceCodeEnd":410,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/mcp/client/oauth.go#L374-L410","documentation":"Returned at oauth.go:391-392 when the callback has no error and the state matched, but the code parameter is empty. Per OAuth2 the AS must return either an error or a code; reaching this branch means the AS issued a non-conformant redirect.","triggerScenarios":"The AS redirected to the callback without ?error and without ?code — e.g. it sent only state, or used a non-standard parameter name, or stripped code from a malformed redirect.","commonSituations":"AS bug; a reverse proxy/load balancer that strips query parameters; custom login handler on the AS that constructs the redirect incorrectly; browser extension interfering with the redirect URL.","solutions":["Inspect the actual redirect URL the AS issued to confirm it includes the code parameter.","Check for proxies or middleware between the AS and SiYuan that may strip query params from the loopback callback.","Report to the AS operator: a standards-compliant authorization-code redirect must include a non-empty code when no error is present."],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":"// After matching state, require a non-empty code; otherwise the AS is non-conformant.\nif callback.Code == \"\" {\n    return fmt.Errorf(\"OAuth callback did not include an authorization code\") // not retriable; report AS bug\n}","preventionTips":["Verify the AS issues standards-compliant authorization-code redirects.","Inspect for proxies or browser extensions stripping query parameters from the loopback redirect."],"tags":["oauth","mcp","callback","protocol","authorization-code"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}