siyuan-note/siyuan · warning

too many pending OIDC login transactions

Error message

too many pending OIDC login transactions

What it means

storeOIDCTransaction also enforces per-client rate limits: at most oidcTransactionPerIP pending (non-completed) transactions from the same client IP, and at most oidcTransactionPerBind transactions sharing the same binding (desktop window binding). When either limit is reached, the new login attempt is rejected with this error. This stops a single client or desktop instance from flooding the pending-login table.

Solutions

  1. Complete or wait for the timeout of the already-pending login attempts, then retry
  2. Clear pending transactions by restarting the kernel or waiting for oidcTransactionTimeout expiry
  3. Deploy a reverse-proxy rate limiter or raise oidcTransactionPerIP/oidcTransactionPerBind if many users legitimately share an egress IP
Defensive patterns

Strategy: retry

Try / catch

if err := storeOIDCTransaction(tx); err != nil {
    if strings.Contains(err.Error(), "too many pending") {
        // wait for the existing pending transactions to expire, then retry once
        time.Sleep(oidcTransactionTimeout)
        err = storeOIDCTransaction(tx)
    }
}

Prevention

When it happens

Trigger: OIDCStart or OIDCValidateStart from an IP that already has oidcTransactionPerIP pending transactions, or a desktop/validate flow whose binding already has oidcTransactionPerBind pending transactions.

Common situations: A user repeatedly clicking 'Sign in with OIDC' without finishing any login; a corporate NAT sharing one IP among many users; a desktop app that restarted with pending transactions still counted.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/18ca14bbfe9cf404. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/oidc.go:670

		transaction.Done = make(chan struct{})
	}
	if len(oidcTransactions.byState) >= oidcTransactionMax {
		return errors.New("OIDC login transaction capacity reached")
	}
	perIP, perBinding := 0, 0
	for _, candidate := range oidcTransactions.byState {
		if candidate.Completed {
			continue
		}
		if transaction.ClientIP != "" && candidate.ClientIP == transaction.ClientIP {
			perIP++
		}
		if transaction.Binding != "" && candidate.Binding == transaction.Binding {
			perBinding++
		}
	}
	if perIP >= oidcTransactionPerIP || perBinding >= oidcTransactionPerBind {
		return errors.New("too many pending OIDC login transactions")
	}
	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()

View on GitHub (pinned to 9f775e8a12)