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
- Update the client app to a release that satisfies config.client.versionControl.requiredVersion.
- If appropriate, relax the server config (AFFiNE_CLIENT_VERSION_CONTROL_REQUIRED_VERSION / config file) to include the installed client range, then redeploy.
- Ensure the client sends the correct version header on every request so clientVersion is not 'unset_or_invalid'.
- 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
- Send a stable X-Client-Version header on every request from the client.
- Run a pre-flight version check against the server's /version endpoint before issuing authenticated requests.
- Keep requiredVersion config aligned across all server nodes in a cluster.
- Test client builds against the configured requiredVersion range in CI.
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.