toeverything/AFFiNE · error · UnsupportedClientVersion

unsupported_client_version

unsupported_client_version

Error message

Unsupported client with version [${clientVersion}], required version is [${requiredVersion}].

What it means

The client app version fails the server's semver gate. In checkUserSessionClientVersion the guard reads the version from the request header, falls back to session.refreshClientVersion / signInClientVersion, then runs it through checkClientVersion against config.client.versionControl.requiredVersion and a hard-coded HARD_REQUIRED_VERSION floor. If it fails, the session is revoked (native auth session) or signed out (cookie session) first, and — unless the route is public with a cookie session — UnsupportedClientVersion is thrown with the offending clientVersion and the requiredVersion. Category is action_forbidden; the data payload exposes both versions for the UI.

Source

Thrown at packages/backend/server/src/core/auth/guard.ts:256

      .authSessionId;
    if (authSessionId) {
      await this.authSessions.revoke(
        authSessionId,
        'unsupported_client_version',
        session.user.id
      );
    } else {
      await this.auth.signOut(session.sessionId);
    }
    if (res && !authSessionId) {
      await this.auth.refreshCookies(res, session.sessionId);
    }

    if (isPublic && !authSessionId) {
      return false;
    }

    throw new UnsupportedClientVersion({
      clientVersion: clientVersion ?? 'unset_or_invalid',
      requiredVersion: versionCheckResult.requiredVersion,
    });
  }

  private getVersionRange(versionRange: string): semver.Range | null {
    if (this.cachedVersionRange.has(versionRange)) {
      // oxlint-disable-next-line typescript/no-non-null-assertion
      return this.cachedVersionRange.get(versionRange)!;
    }

    let range: semver.Range | null = null;
    try {
      range = new semver.Range(versionRange, { loose: false });
      if (!semver.validRange(range)) {
        range = null;
      }
    } catch {

View on GitHub (pinned to 26c515e050)

Solutions

  1. Update the client app to a release that satisfies config.client.versionControl.requiredVersion.
  2. If appropriate, relax the server config (AFFiNE_CLIENT_VERSION_CONTROL_REQUIRED_VERSION / config file) to include the installed client range, then redeploy.
  3. Ensure the client sends the correct version header on every request so clientVersion is not 'unset_or_invalid'.
  4. Temporarily disable the gate by setting client.versionControl.enabled=false while upgrading the fleet.

Example fix

# self-hosted: broaden the allowed range
AFFiNE_CLIENT_VERSION_CONTROL_ENABLED=true
AFFiNE_CLIENT_VERSION_CONTROL_REQUIRED_VERSION=">=0.25.0"
Defensive patterns

Strategy: validation

Validate before calling

import semver from 'semver';

function isClientVersionAllowed(
  clientVersion: string | undefined,
  requiredRange: string
): boolean {
  if (!clientVersion) return false;
  try {
    return semver.satisfies(clientVersion, requiredRange, {
      includePrerelease: true,
    });
  } catch {
    return false;
  }
}

// before any authenticated call:
if (!isClientVersionAllowed(APP_VERSION, SERVER_REQUIRED_VERSION)) {
  promptUpgrade();
}

Type guard

function isUnsupportedClientVersion(err: unknown): boolean {
  return (
    !!err &&
    typeof err === 'object' &&
    (err as { code?: string }).code === 'unsupported_client_version'
  );
}

Try / catch

try {
  await api.call();
} catch (err) {
  if (isUnsupportedClientVersion(err)) {
    showUpgradeRequired((err as any).data.requiredVersion);
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: A request whose X-Client-Version header (or stored session signInClientVersion) does not satisfy the configured requiredVersion semver range, while config.client.versionControl.enabled is true. For canary builds the canary date must be current or it falls back to the canary required version. The route is either non-public, or public but using a native auth session (authSessionId present), so the early 'return false' escape is not taken.

Common situations: User is running an old desktop/mobile build against a server that has bumped requiredVersion (e.g. >=0.26.0). A self-hosted admin set client.versionControl.requiredVersion too strictly or left it mismatched across nodes. The client forgot to send the version header, so clientVersion resolves to 'unset_or_invalid'.

Related errors


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