siyuan-note/siyuan · error
OIDC login transaction was not found or has expired
Error message
OIDC login transaction was not found or has expired
What it means
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).
Source
Thrown at kernel/model/oidc.go:745
}
oidcTransactions.byState[transaction.State] = transaction
if transaction.PollToken != "" {
oidcTransactions.byPoll[transaction.PollToken] = transaction.State
}
return nil
}
func claimOIDCTransaction(ctx context.Context, state, binding string,
allowDesktopWithoutBinding bool) (*oidcTransaction, bool, error) {
if state == "" {
return nil, false, errors.New("OIDC state is missing")
}
oidcTransactions.Lock()
cleanupOIDCTransactionsLocked()
transaction := oidcTransactions.byState[state]
if transaction == nil {
oidcTransactions.Unlock()
return nil, false, errors.New("OIDC login transaction was not found or has expired")
}
if transaction.ConfigVersion != oidcConfigurationVersion(Conf.GetOIDC()) {
deleteOIDCTransactionLocked(state)
oidcTransactions.Unlock()
return nil, false, errors.New("OIDC configuration changed during login")
}
if !(allowDesktopWithoutBinding && (transaction.Flow == oidcFlowDesktop || transaction.Flow == oidcFlowValidate)) &&
(binding == "" || binding != transaction.Binding) {
oidcTransactions.Unlock()
return nil, false, errors.New("OIDC login binding does not match")
}
if !transaction.Claimed {
transaction.Claimed = true
copy := *transaction
oidcTransactions.Unlock()
return ©, false, nil
}
done := transaction.DoneView on GitHub (pinned to 251596fc0d)
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.
Defensive patterns
Strategy: try-catch
Try / catch
// Treat not-found/expired as 'restart the flow', not a hard failure.
tx, completed, err := claimOIDCTransaction(ctx, state, binding, allowDesktop)
if err != nil && strings.Contains(err.Error(), "was not found or has expired") {
// prompt user to restart login; do not retry the same state
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: User idles at the IdP consent screen past the timeout; SiYuan restart during login; multi-instance deployment without sticky sessions for the OIDC endpoints.
Related errors
- OIDC state is missing
- OIDC login binding does not match
- OIDC validation transaction was not found or has expired
- OIDC authorization code is missing
- Save OIDC login session failed
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/a75b72d1bef2e9fb.
Report an issue: GitHub.