{"record":{"id":"f6d923bd007d26cc","repo":"toeverything/AFFiNE","slug":"auth-session-temporarily-unavailable","errorCode":"auth_session_temporarily_unavailable","errorMessage":"Auth session service is temporarily unavailable.","messagePattern":"Auth session service is temporarily unavailable\\.","errorType":"exception","errorClass":"AuthSessionTemporarilyUnavailable","httpStatus":504,"severity":"warning","filePath":"packages/backend/server/src/core/auth/access-token.ts","lineNumber":63,"sourceCode":"    const ttl = this.config.auth.token.accessTokenTtl;\n    for (let attempt = 0; attempt < SIGNING_KEY_RETRY_LIMIT; attempt++) {\n      const issuedAt = Math.floor(Date.now() / 1000);\n      const expiresAtSeconds = issuedAt + ttl;\n      const expiresAt = new Date(expiresAtSeconds * 1000);\n      const key = await this.keys.active();\n      const token = signAuthSessionAccessToken(\n        userId,\n        authSessionId,\n        key.id,\n        key.secret,\n        issuedAt,\n        expiresAtSeconds\n      );\n      if ((await this.keys.active()).id === key.id) {\n        return { token, expiresAt };\n      }\n    }\n    throw new AuthSessionTemporarilyUnavailable();\n  }\n\n  async verify(token: string): Promise<AuthSessionPrincipal> {\n    const keyId = authSessionAccessTokenKeyId(token);\n    if (!keyId) {\n      throw new SessionAccessTokenError('ACCESS_TOKEN_INVALID');\n    }\n    const key = await this.keys.verify(keyId);\n    if (!key) throw new SessionAccessTokenError('ACCESS_TOKEN_INVALID');\n    const verified = verifyAuthSessionAccessToken(\n      token,\n      keyId,\n      key.secret,\n      Math.floor(Date.now() / 1000)\n    );\n    if (verified.status !== 'valid') {\n      throw new SessionAccessTokenError(\n        verified.status === 'expired'","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/core/auth/access-token.ts#L45-L81","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"import { AuthSessionTemporarilyUnavailable } from '...';\n\nasync function exchangeWithRetry(req, code, meta, attempts = 3) {\n  for (let i = 0; i < attempts; i++) {\n    try { return await sessionExchange.exchange(req, code, meta); }\n    catch (e) {\n      if (e instanceof AuthSessionTemporarilyUnavailable && i < attempts - 1) {\n        await sleep(backoffMs(i)); continue;\n      }\n      throw e;\n    }\n  }\n}","preventionTips":["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."],"tags":["authentication","signing-key","race-condition","retry","internal"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}