siyuan-note/siyuan · error

OIDC login transaction capacity reached

Error message

OIDC login transaction capacity reached

What it means

storeOIDCTransaction refuses new entries once the in-memory store holds oidcTransactionMax (512) transactions server-wide. This bounds memory; expired entries are only reaped lazily on the next store/claim call via cleanupOIDCTransactionsLocked.

Source

Thrown at kernel/model/oidc.go:711

		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 251596fc0d)

Solutions

  1. Wait roughly 10 minutes for expiry reap to free slots.
  2. Restart the kernel to clear the in-memory transaction store if urgent.
  3. Investigate source IPs/bindings - if a single source dominates, it is likely abuse and should be rate-limited at the edge.
Defensive patterns

Strategy: retry

Try / catch

// On capacity errors, back off and retry; the lazy reaper frees slots as transactions expire.
if err := storeOIDCTransaction(tx); err != nil {
    if strings.Contains(err.Error(), "capacity reached") {
        time.Sleep(retryBackoff)
        // retry start, or surface 'service busy, retry shortly'
    }
}

Prevention

When it happens

Trigger: A burst of /api/system/oidc/start calls (legitimate or abusive) that outpaces the 10-minute transaction expiry reap; clients that start login but never complete the IdP callback.

Common situations: Bot or scanner hitting the start endpoint; many users behind one NAT starting flows simultaneously; abandoned browser tabs each holding a transaction.

Related errors


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