{"record":{"id":"07debdf0733bdfad","repo":"siyuan-note/siyuan","slug":"oauth-flow-is-missing-or-expired","errorCode":null,"errorMessage":"OAuth flow is missing or expired","messagePattern":"OAuth flow is missing or expired","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"kernel/mcp/client/oauth.go","lineNumber":592,"sourceCode":"\t}\n\treturn base64.RawURLEncoding.EncodeToString(data), nil\n}\n\nfunc removeOAuthFlow(flowID string, flow *oauthFlow) {\n\toauthFlows.Lock()\n\tif oauthFlows.items[flowID] == flow {\n\t\tdelete(oauthFlows.items, flowID)\n\t}\n\toauthFlows.Unlock()\n}\n\nfunc CompleteMCPOAuth(flowID, code, state, callbackError, issuer string) error {\n\toauthFlows.Lock()\n\tflow := oauthFlows.items[flowID]\n\tif flow == nil || time.Now().After(flow.Expires) {\n\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}","sourceCodeStart":574,"sourceCodeEnd":610,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/mcp/client/oauth.go#L574-L610","documentation":"Returned at oauth.go:589-592 by CompleteMCPOAuth when the flowID lookup in oauthFlows.items returns nil OR the flow's Expires time has passed (5-minute lifetime, oauthAuthorizationTimeout). The flow either was never created, was already consumed and deleted, or expired before the callback arrived.","triggerScenarios":"The OAuth callback HTTP request hits /api/ai/mcp/oauth/callback/<flowID> after Authorize already returned (timeout or ctx cancel which deleted the flow), or after the 5-minute Expires window, or with a flowID that was never registered (process restart wiped the in-memory map).","commonSituations":"User completes browser consent more than 5 minutes after starting; the original Authorize call already timed out (error 328) and removed the flow; duplicate callback (second tab) arrives after the first consumed the flow; kernel restarted between authorization start and callback.","solutions":["Re-initiate the OAuth authorization and complete the browser consent within the 5-minute window, then let exactly one callback through.","Avoid duplicate callback tabs; the first callback consumes and deletes the flow.","If the kernel was restarted, the in-memory flow is gone — start a fresh authorization; flows are intentionally not persisted.","Confirm the callback flowID in the URL matches the one issued during authorization (no truncation by the AS or browser)."],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":"// CompleteMCPOAuth called by the callback handler — flow may already be gone.\nerr := CompleteMCPOAuth(flowID, code, state, callbackError, issuer)\nif err != nil && strings.Contains(err.Error(), \"OAuth flow is missing or expired\") {\n    // Render a user-facing page: 'authorization expired, please retry from SiYuan'.\n    http.Error(w, \"authorization flow expired; restart the authorization\", http.StatusGone)\n    return\n}","preventionTips":["Complete browser consent within the 5-minute flow lifetime.","Avoid duplicate callback tabs (only the first callback is honored).","Do not restart the kernel between starting authorization and finishing the callback — flows are in-memory only."],"tags":["oauth","mcp","callback","flow-expiry","in-memory-state","interactive"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}