{"record":{"id":"8ee0af86490bb30d","repo":"siyuan-note/siyuan","slug":"wait-for-oidc-login-transaction-failed-w","errorCode":null,"errorMessage":"wait for OIDC login transaction failed: %w","messagePattern":"wait for OIDC login transaction failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"kernel/model/oidc.go","lineNumber":768,"sourceCode":"\t\treturn nil, false, errors.New(\"OIDC configuration changed during login\")\n\t}\n\tif !(allowDesktopWithoutBinding && (transaction.Flow == oidcFlowDesktop || transaction.Flow == oidcFlowValidate)) &&\n\t\t(binding == \"\" || binding != transaction.Binding) {\n\t\toidcTransactions.Unlock()\n\t\treturn nil, false, errors.New(\"OIDC login binding does not match\")\n\t}\n\tif !transaction.Claimed {\n\t\ttransaction.Claimed = true\n\t\tcopy := *transaction\n\t\toidcTransactions.Unlock()\n\t\treturn &copy, false, nil\n\t}\n\tdone := transaction.Done\n\toidcTransactions.Unlock()\n\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn nil, false, fmt.Errorf(\"wait for OIDC login transaction failed: %w\", ctx.Err())\n\tcase <-done:\n\t}\n\n\toidcTransactions.Lock()\n\tdefer oidcTransactions.Unlock()\n\ttransaction = oidcTransactions.byState[state]\n\tif transaction == nil || !transaction.Completed {\n\t\treturn nil, false, errors.New(\"OIDC login transaction was not found or has expired\")\n\t}\n\tcopy := *transaction\n\treturn &copy, true, nil\n}\n\nfunc completeOIDCTransaction(state string, success bool, message string) {\n\toidcTransactions.Lock()\n\tdefer oidcTransactions.Unlock()\n\ttransaction := oidcTransactions.byState[state]\n\tif transaction == nil {","sourceCodeStart":750,"sourceCodeEnd":786,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/oidc.go#L750-L786","documentation":"The poll/callback request blocked on the transaction's Done channel and its context was cancelled first. The %w wraps ctx.Err(), typically context.DeadlineExceeded or context.Canceled, meaning the IdP callback had not arrived within the request deadline.","triggerScenarios":"Long-poll endpoint where the user takes longer at the IdP than the request deadline allows; the HTTP/proxy context was cancelled (client disconnect, proxy timeout) before the callback completed the transaction.","commonSituations":"User idles on the IdP consent screen; reverse proxy with a short proxy_read_timeout that closes the long-poll; client aborts the poll request early.","solutions":["Raise the long-poll/request timeout on the reverse proxy (>= 60s, more than a realistic consent delay).","Have the user complete IdP consent promptly.","For desktop/validate flows, simply retry the poll - the transaction state is preserved across poll requests."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// For desktop/validate polls, ctx cancellation is recoverable - just poll again.\ntx, completed, err := claimOIDCTransaction(ctx, state, binding, allowDesktop)\nif err != nil {\n    if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {\n        // transient: retry the poll request, transaction is preserved\n        return pollAgain\n    }\n}","preventionTips":["Set reverse-proxy read timeouts to at least 60s so the long-poll survives a normal consent delay.","For long-poll flows, retry the poll on context cancellation rather than failing the login.","Encourage users to complete IdP consent promptly."],"tags":["oidc","timeout","polling","concurrency"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}