siyuan-note/siyuan · error
OIDC validation transaction was not found or has expired
Error message
OIDC validation transaction was not found or has expired
What it means
activateOIDCValidation looks up a pending OIDC validation flow by its state and verifies it is a validate-flow transaction with a matching binding that completed successfully. If no transaction exists for the state, or any precondition (flow type, binding match, completed, success) fails, it reports that the transaction was not found or expired. Transactions are short-lived by design and are purged by cleanupOIDCTransactionsLocked.
Solutions
- Restart the validation flow from OIDCValidateStart to obtain a fresh poll token/state, then retry activation
- Verify the client sends the same binding that was used when the validation transaction was started
- Check that token exchange (finishOIDCExchange) completed successfully before calling activate; inspect transaction.Completed/Success
- Reduce the gap between poll success and activation or increase the transaction TTL if users routinely need longer
- After a kernel restart, discard old poll tokens — they are in-memory and cannot be recovered
Example fix
// before ok, err := model.OIDCValidateActivate(stalePollToken, binding) // expired // after // re-run validation first start, err := model.OIDCValidateStart(redirectURL) // ... poll ... then: ok, err := model.OIDCValidateActivate(start.PollToken, binding)
Defensive patterns
Strategy: try-catch
Validate before calling
// Go: before activating, confirm the flow is still pending and fresh
// (client-side: only activate within the documented TTL and after poll reports success)
if !pollResult.Success || time.Since(pollResult.CompletedAt) > ttl { restartValidation() } Type guard
func canActivate(txn *model.OIDCTransactionInfo) bool {
return txn != nil && txn.Flow == "validate" && txn.Completed && txn.Success && !txn.Activated
} Try / catch
ok, err := model.OIDCValidateActivate(pollToken, binding)
if err != nil && strings.Contains(err.Error(), "not found or has expired") {
// restart the validation flow to get a fresh transaction
return restartOIDCValidation()
} Prevention
- Activate promptly after poll success; do not leave the flow idle past the transaction TTL
- Never cache poll tokens across kernel restarts — they are in-memory
- Always send the exact binding value issued at flow start
- Disable the activate button once it has succeeded to avoid double activation
When it happens
Trigger: Calling OIDCValidateActivate with a pollToken whose state no longer resolves (expired/purged transaction), a poll token from a non-validate flow, a mismatched binding value, or activating before token exchange completed/failed (transaction.Completed or transaction.Success false). Also raised by tests TestActivateOIDCValidationAppliesCandidateOnce, TestActivateOIDCValidationRejectsChangedConfiguration, TestCancelOIDCValidationPreventsActivation.
Common situations: User takes too long between scanning/polling and activation so the transaction TTL expires; server restart wipes in-memory oidcTransactions; frontend passes the wrong binding (e.g. stale browser tab); double-clicking activate after cancel already deleted the transaction.
Related errors
- OIDC login transaction was not found or has expired
- OIDC validation configuration is missing
- remote access requires at least one authentication method
- 376
- A loopback OIDC redirect URL is required for local access
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/2c17a69c7c682548.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/oidc.go:774
state := oidcTransactions.byPoll[pollToken]
transaction := oidcTransactions.byState[state]
if transaction == nil || (transaction.Flow != oidcFlowDesktop && transaction.Flow != oidcFlowValidate) ||
binding == "" || binding != transaction.Binding {
return nil, false
}
copy := *transaction
return ©, true
}
func activateOIDCValidation(pollToken, binding string) (activated bool, err error) {
oidcTransactions.Lock()
defer oidcTransactions.Unlock()
cleanupOIDCTransactionsLocked()
state := oidcTransactions.byPoll[pollToken]
transaction := oidcTransactions.byState[state]
if transaction == nil || transaction.Flow != oidcFlowValidate || transaction.Binding == "" ||
binding == "" || transaction.Binding != binding || !transaction.Completed || !transaction.Success {
return false, errors.New("OIDC validation transaction was not found or has expired")
}
if transaction.Activated {
return false, nil
}
if transaction.Config == nil {
return false, errors.New("OIDC validation configuration is missing")
}
configurationChanged, swapped := Conf.CompareAndSetOIDC(transaction.ConfigVersion, transaction.Config)
if !swapped {
deleteOIDCTransactionLocked(state)
return false, errors.New("OIDC configuration changed during validation")
}
transaction.Config = nil
transaction.Activated = true
return configurationChanged, nil
}
func cancelOIDCValidation(pollToken, binding string) bool {View on GitHub (pinned to 9f775e8a12)