{"record":{"id":"a75b72d1bef2e9fb","repo":"siyuan-note/siyuan","slug":"oidc-login-transaction-was-not-found-or-has-expire","errorCode":null,"errorMessage":"OIDC login transaction was not found or has expired","messagePattern":"OIDC login transaction was not found or has expired","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/oidc.go","lineNumber":689,"sourceCode":"\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()\n\t\treturn nil, false, errors.New(\"OIDC login transaction was not found or has expired\")\n\t}\n\tif transaction.ConfigVersion != oidcConfigurationVersion(Conf.GetOIDC()) {\n\t\tdeleteOIDCTransactionLocked(state)\n\t\toidcTransactions.Unlock()\n\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","sourceCodeStart":671,"sourceCodeEnd":707,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/oidc.go#L671-L707","documentation":"claimOIDCTransaction looks up the pending transaction by state after cleaning up expired entries. If no transaction with that state exists — either it expired after oidcTransactionTimeout, was never created, or was already removed — the callback cannot be matched to a login and this error is returned.","triggerScenarios":"OIDCCallback/OIDCMobileCallback arrives with a state value that is not in oidcTransactions.byState after cleanupOIDCTransactionsLocked: expired login, server restart, state from a different instance, or double-claimed then deleted transaction.","commonSituations":"User leaves the IdP login page open longer than the transaction timeout, then completes it; kernel restarted between login start and callback; load-balanced setup sending start and callback to different nodes.","solutions":["Restart the OIDC login from scratch — a fresh state will be issued","Complete the IdP login promptly, within oidcTransactionTimeout","If running multiple instances, enable sticky sessions or a shared transaction store so start and callback hit the same node"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"tx, done, err := claimOIDCTransaction(ctx, state, binding, false)\nif err != nil {\n    // treat as expired: redirect the user to restart the OIDC login\n    redirectToLoginStart(w, r)\n    return\n}","preventionTips":["Complete the IdP login promptly, within the transaction timeout","Avoid restarting the kernel mid-login","Use sticky sessions or a single instance for OIDC flows"],"tags":["oidc","session-expired","state"],"backgroundTag":"record-not-found","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"}