{"record":{"id":"18ca14bbfe9cf404","repo":"siyuan-note/siyuan","slug":"too-many-pending-oidc-login-transactions","errorCode":null,"errorMessage":"too many pending OIDC login transactions","messagePattern":"too many pending OIDC login transactions","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"kernel/model/oidc.go","lineNumber":726,"sourceCode":"\t\ttransaction.Done = make(chan struct{})\n\t}\n\tif len(oidcTransactions.byState) >= oidcTransactionMax {\n\t\treturn errors.New(\"OIDC login transaction capacity reached\")\n\t}\n\tperIP, perBinding := 0, 0\n\tfor _, candidate := range oidcTransactions.byState {\n\t\tif candidate.Completed {\n\t\t\tcontinue\n\t\t}\n\t\tif transaction.ClientIP != \"\" && candidate.ClientIP == transaction.ClientIP {\n\t\t\tperIP++\n\t\t}\n\t\tif transaction.Binding != \"\" && candidate.Binding == transaction.Binding {\n\t\t\tperBinding++\n\t\t}\n\t}\n\tif perIP >= oidcTransactionPerIP || perBinding >= oidcTransactionPerBind {\n\t\treturn errors.New(\"too many pending OIDC login transactions\")\n\t}\n\toidcTransactions.byState[transaction.State] = transaction\n\tif transaction.PollToken != \"\" {\n\t\toidcTransactions.byPoll[transaction.PollToken] = transaction.State\n\t}\n\treturn nil\n}\n\nfunc claimOIDCTransaction(ctx context.Context, state, binding string,\n\tallowDesktopWithoutBinding bool) (*oidcTransaction, bool, error) {\n\tif state == \"\" {\n\t\treturn nil, false, errors.New(\"OIDC state is missing\")\n\t}\n\toidcTransactions.Lock()\n\tcleanupOIDCTransactionsLocked()\n\ttransaction := oidcTransactions.byState[state]\n\tif transaction == nil {\n\t\toidcTransactions.Unlock()","sourceCodeStart":708,"sourceCodeEnd":744,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/oidc.go#L708-L744","documentation":"storeOIDCTransaction enforces per-source limits: oidcTransactionPerIP (32) pending transactions per client IP and oidcTransactionPerBind (8) per binding. Exceeding either rejects the new transaction to keep one source from saturating the store.","triggerScenarios":"A single client IP holds >= 32 pending transactions, or a single binding holds >= 8, when a new start arrives.","commonSituations":"User opens many login tabs; a misbehaving client auto-retries start in a loop on failure; shared office NAT concentrating many users behind one IP.","solutions":["Close other pending login attempts for this user/session and retry.","Wait for the older pending transactions to expire (up to 10 minutes).","Confirm the client is not auto-retrying /api/system/oidc/start on transient failures."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Per-source limit hit - back off; do not hammer start.\nif err := storeOIDCTransaction(tx); err != nil && strings.Contains(err.Error(), \"too many pending\") {\n    respondTooManyPending(c) // ask client to close other login tabs and retry\n}","preventionTips":["Drive at most one login flow per session/binding at a time.","Do not auto-retry /api/system/oidc/start in a loop on transient failures.","Surface 'close other login attempts and retry' to the end user."],"tags":["oidc","rate-limit","concurrency","per-source"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}