{"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":745,"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":727,"sourceCodeEnd":763,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/oidc.go#L727-L763","documentation":"A state was supplied but no matching transaction exists in byState. Either the 10-minute transaction timeout reaped it, it was already consumed/activated, it was deleted by a config-change path, or it was created on a different kernel instance (the store is in-memory, not shared).","triggerScenarios":"Callback or poll arriving more than 10 minutes after start; replay of an already-used state; SiYuan restarted mid-flow (store lost); load balancer routed the callback to a different instance.","commonSituations":"User idles at the IdP consent screen past the timeout; SiYuan restart during login; multi-instance deployment without sticky sessions for the OIDC endpoints.","solutions":["Restart the login flow to get a fresh transaction.","If running multiple SiYuan instances, pin /api/system/oidc/* to the originating instance (sticky sessions).","Confirm the kernel was not restarted between start and callback."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Treat not-found/expired as 'restart the flow', not a hard failure.\ntx, completed, err := claimOIDCTransaction(ctx, state, binding, allowDesktop)\nif err != nil && strings.Contains(err.Error(), \"was not found or has expired\") {\n    // prompt user to restart login; do not retry the same state\n}","preventionTips":["Complete the IdP callback within the 10-minute transaction window.","Pin /api/system/oidc/* to the originating instance via sticky sessions in multi-instance deployments.","Avoid restarting the kernel during active logins - the store is in-memory only."],"tags":["oidc","authentication","state","expiry","distributed"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}