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

  1. Restart the validation flow from OIDCValidateStart to obtain a fresh poll token/state, then retry activation
  2. Verify the client sends the same binding that was used when the validation transaction was started
  3. Check that token exchange (finishOIDCExchange) completed successfully before calling activate; inspect transaction.Completed/Success
  4. Reduce the gap between poll success and activation or increase the transaction TTL if users routinely need longer
  5. 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

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


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 &copy, 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)