toeverything/AFFiNE · warning · AuthSessionTemporarilyUnavailable

auth_session_temporarily_unavailable

auth_session_temporarily_unavailable

Error message

Auth session service is temporarily unavailable.

What it means

Thrown by `AccessTokenService.sign` after `SIGNING_KEY_RETRY_LIMIT` (3) attempts. Each attempt signs a token with the currently-active key, then re-reads the active key to confirm it hasn't changed; if the active key id differs on every retry (a concurrent rotation keeps winning the race), signing gives up. Maps to HTTP 504 (network_error).

Source

Thrown at packages/backend/server/src/core/auth/access-token.ts:63

    const ttl = this.config.auth.token.accessTokenTtl;
    for (let attempt = 0; attempt < SIGNING_KEY_RETRY_LIMIT; attempt++) {
      const issuedAt = Math.floor(Date.now() / 1000);
      const expiresAtSeconds = issuedAt + ttl;
      const expiresAt = new Date(expiresAtSeconds * 1000);
      const key = await this.keys.active();
      const token = signAuthSessionAccessToken(
        userId,
        authSessionId,
        key.id,
        key.secret,
        issuedAt,
        expiresAtSeconds
      );
      if ((await this.keys.active()).id === key.id) {
        return { token, expiresAt };
      }
    }
    throw new AuthSessionTemporarilyUnavailable();
  }

  async verify(token: string): Promise<AuthSessionPrincipal> {
    const keyId = authSessionAccessTokenKeyId(token);
    if (!keyId) {
      throw new SessionAccessTokenError('ACCESS_TOKEN_INVALID');
    }
    const key = await this.keys.verify(keyId);
    if (!key) throw new SessionAccessTokenError('ACCESS_TOKEN_INVALID');
    const verified = verifyAuthSessionAccessToken(
      token,
      keyId,
      key.secret,
      Math.floor(Date.now() / 1000)
    );
    if (verified.status !== 'valid') {
      throw new SessionAccessTokenError(
        verified.status === 'expired'

View on GitHub (pinned to 26c515e050)

Solutions

  1. Retry the token request after a short backoff — a single rotation completes quickly and the ring stabilizes.
  2. Stop concurrent rotation sources: run only one key-rotation actor at a time (serialize via a lock or a single scheduler).
  3. Inspect `auth.signing_key.rotated` events in logs to confirm whether a rotation storm is occurring.
  4. Verify the event bus (`EventBus.broadcast`) is healthy so all instances converge on the same active key promptly.
Defensive patterns

Strategy: retry

Try / catch

import { AuthSessionTemporarilyUnavailable } from '...';

async function exchangeWithRetry(req, code, meta, attempts = 3) {
  for (let i = 0; i < attempts; i++) {
    try { return await sessionExchange.exchange(req, code, meta); }
    catch (e) {
      if (e instanceof AuthSessionTemporarilyUnavailable && i < attempts - 1) {
        await sleep(backoffMs(i)); continue;
      }
      throw e;
    }
  }
}

Prevention

When it happens

Trigger: Two or more admins/bots rotating the auth signing key simultaneously, or a `auth.signing_keys.changed` broadcast storm that keeps flipping the active key between the sign and the confirm read. Also reachable if the key ring reconciliation repeatedly returns a different active key than the one used to sign.

Common situations: Running an automated key-rotation job while an operator also rotates via the admin UI, multi-instance deployments where the event bus broadcast lags behind rapid rotations, or a misbehaving `onSigningKeysChanged` handler that mutates state on every tick.

Related errors


AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12). Data as JSON: /api/errors/f6d923bd007d26cc. Report an issue: GitHub.