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
- Retry the token request after a short backoff — a single rotation completes quickly and the ring stabilizes.
- Stop concurrent rotation sources: run only one key-rotation actor at a time (serialize via a lock or a single scheduler).
- Inspect `auth.signing_key.rotated` events in logs to confirm whether a rotation storm is occurring.
- 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
- Serialize signing-key rotations so only one actor rotates at a time.
- Monitor `auth.signing_key.rotated` event frequency to detect rotation storms.
- Keep the event bus healthy so all instances converge on the active key quickly.
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
- wrong_sign_in_credentials
- DatabaseBlockError
- DatabaseBlockError
- ErrorCode.TransformerError
- authentication_required
AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12).
Data as JSON: /api/errors/f6d923bd007d26cc.
Report an issue: GitHub.