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 &copy, false, nil
	}
	done := transaction.Done

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Restart the login flow to get a fresh transaction.
  2. If running multiple SiYuan instances, pin /api/system/oidc/* to the originating instance (sticky sessions).
  3. 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

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


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/a75b72d1bef2e9fb. Report an issue: GitHub.