{"record":{"id":"c953f19a62d22df3","repo":"siyuan-note/siyuan","slug":"oauth-callback-was-already-handled","errorCode":null,"errorMessage":"OAuth callback was already handled","messagePattern":"OAuth callback was already handled","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"kernel/mcp/client/oauth.go","lineNumber":608,"sourceCode":"\t\tdelete(oauthFlows.items, flowID)\n\t\toauthFlows.Unlock()\n\t\treturn fmt.Errorf(\"OAuth flow is missing or expired\")\n\t}\n\tif state != flow.State {\n\t\toauthFlows.Unlock()\n\t\treturn fmt.Errorf(\"OAuth state mismatch\")\n\t}\n\tif issuer != \"\" && issuer != flow.Issuer {\n\t\toauthFlows.Unlock()\n\t\treturn fmt.Errorf(\"OAuth issuer mismatch\")\n\t}\n\tdelete(oauthFlows.items, flowID)\n\toauthFlows.Unlock()\n\tselect {\n\tcase flow.Result <- oauthCallbackResult{Code: code, State: state, Error: callbackError}:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"OAuth callback was already handled\")\n\t}\n}\n\nfunc IsLoopbackCallback(remoteAddr string) bool {\n\thost, _, err := net.SplitHostPort(remoteAddr)\n\tif err != nil {\n\t\treturn false\n\t}\n\tip := net.ParseIP(host)\n\treturn ip != nil && ip.IsLoopback()\n}\n\nfunc refreshOAuthCredential(ctx context.Context, client *http.Client, credential oauthCredential) (oauthCredential, bool, error) {\n\tvalues := url.Values{\n\t\t\"grant_type\":    {\"refresh_token\"},\n\t\t\"refresh_token\": {credential.RefreshToken},\n\t\t\"resource\":      {credential.Resource},\n\t}","sourceCodeStart":590,"sourceCodeEnd":626,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/mcp/client/oauth.go#L590-L626","documentation":"After passing validation, CompleteMCPOAuth delivers the callback result to the flow's buffered Result channel in a non-blocking select. If no reader is waiting and the channel is full/already delivered, the callback was already handled — the waiting goroutine that consumes the result already finished (or the flow completed). This error prevents double delivery.","triggerScenarios":"CompleteMCPOAuth invoked a second time for the same logical callback after the Result channel was already consumed: duplicate HTTP callback requests (browser retry/prefetch), the user reloading the callback URL, or two callback routes firing for one flow.","commonSituations":"Browser resends the redirect request; user refreshes the callback page; a proxy retries the request; monitoring/crawler hits the callback URL with the same parameters after completion.","solutions":["Ignore this error if the OAuth flow actually succeeded — it typically indicates a benign duplicate callback.","Ensure the callback endpoint is called once per flow; deduplicate on the client/proxy side.","Check for browser prefetch or extensions reissuing the redirect and disable them for the callback.","If it appears without a successful login, restart the authorization flow.","Verify no custom automation double-posts the callback."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := CompleteMCPOAuth(flowID, code, state, callbackErr, issuer); err != nil {\n    if strings.Contains(err.Error(), \"already handled\") {\n        log.Info(\"duplicate OAuth callback ignored\") // benign; flow already finished\n    }\n}","preventionTips":["Treat duplicate callbacks as benign when login already succeeded","Disable prefetch/aggressive extensions for the callback URL","Deduplicate callback requests at the proxy or handler level","Do not re-post callbacks in automation scripts"],"tags":["oauth","mcp","duplicate-callback","concurrency"],"backgroundTag":"oauth-callback-already-handled","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}