siyuan-note/siyuan · error

OIDC login transaction capacity reached

Error message

OIDC login transaction capacity reached

What it means

storeOIDCTransaction registers a new pending OIDC login transaction and enforces a global capacity limit of oidcTransactionMax entries in the byState map (after expiring stale entries). When the map is at capacity, new logins are rejected with this error to bound memory usage and prevent state-forgery resource exhaustion.

Solutions

  1. Wait for existing transactions to expire (oidcTransactionTimeout) or complete, then retry the login
  2. Reduce load: close abandoned login tabs and investigate the source of the request flood (rate-limit at a reverse proxy)
  3. If legitimate capacity is chronously exceeded, raise oidcTransactionMax in the source or shorten oidcTransactionTimeout
Defensive patterns

Strategy: retry

Try / catch

if err := storeOIDCTransaction(tx); err != nil {
    if strings.Contains(err.Error(), "capacity reached") {
        time.Sleep(time.Second) // let expired entries be cleaned
        err = storeOIDCTransaction(tx)
    }
}

Prevention

When it happens

Trigger: OIDCStart or OIDCValidateStart attempts to create a login transaction while oidcTransactions.byState already holds oidcTransactionMax entries even after cleanupOIDCTransactionsLocked removed expired/completed ones.

Common situations: Login-flood/DoS traffic hitting the OIDC start endpoint; many abandoned desktop/validate login tabs that never complete; a burst of mobile login attempts.

Related errors


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

Appendix: source

Thrown at kernel/model/oidc.go:655

		if err != nil {
			return nil, err
		}
	}
	return &oidcTransaction{State: state, Nonce: nonce, CodeVerifier: verifier, PollToken: pollToken, Binding: binding,
		ClientIP: clientIP,
		Flow:     input.Flow, RedirectURL: redirectURL, To: input.To, ConfigVersion: oidcConfigurationVersion(Conf.GetOIDC()), RememberMe: input.RememberMe,
		ExpiresAt: time.Now().Add(oidcTransactionTimeout), Done: make(chan struct{})}, nil
}

func storeOIDCTransaction(transaction *oidcTransaction) error {
	oidcTransactions.Lock()
	defer oidcTransactions.Unlock()
	cleanupOIDCTransactionsLocked()
	if transaction.Done == nil {
		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 != "" {

View on GitHub (pinned to 9f775e8a12)