toeverything/AFFiNE · error · UnsupportedClientVersion

unsupported_client_version

unsupported_client_version

Error message

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

What it means

Thrown by RealtimeGateway.assertVersion when the clientVersion on a realtime envelope is missing, unparseable, fails semver.valid, or does not satisfy MIN_REALTIME_CLIENT_VERSION (>=0.26.0-0, prereleases included). It blocks all realtime:request/subscribe/unsubscribe traffic from incompatible clients to prevent protocol drift.

Source

Thrown at packages/backend/server/src/core/realtime/gateway.ts:165

  @OnEvent('realtime.topic.changed', { suppressError: true })
  onRealtimeTopicChanged(payload: RealtimePublishPayload) {
    try {
      this.publisher.publishLocal(payload);
    } catch (error) {
      this.logger.error('Failed to publish realtime event', error);
    }
  }

  private assertVersion(clientVersion?: string) {
    const normalized = clientVersion
      ? normalizeRealtimeClientVersion(clientVersion)
      : null;
    if (
      !normalized ||
      !semver.valid(normalized) ||
      !MIN_REALTIME_CLIENT_VERSION.test(normalized)
    ) {
      throw new UnsupportedClientVersion({
        clientVersion: clientVersion ?? 'unset_or_invalid',
        requiredVersion: '>=0.26.0',
      });
    }
  }
}

View on GitHub (pinned to 26c515e050)

Solutions

  1. Upgrade the client to >=0.26.0 and ensure the realtime envelope includes a valid semver clientVersion.
  2. For canary builds, set env.namespaces.canary and use the canary-date version format.
  3. If you control the client, send the same version reported to the version guard on /api/version.
  4. As a last resort on a self-host dev box, downgrade the server or pin a compatible client.

Example fix

// before
socket.emit('realtime:subscribe', { topic, input });

// after
socket.emit('realtime:subscribe', {
  clientVersion: APP_VERSION, // e.g. '0.26.0'
  topic,
  input,
});
Defensive patterns

Strategy: validation

Validate before calling

import semver from 'semver';
const MIN = new semver.Range('>=0.26.0-0', { includePrerelease: true });
function clientOk(v?: string) {
  return !!v && semver.valid(v) != null && MIN.test(v);
}
if (!clientOk(APP_VERSION)) {
  // block realtime connect; show upgrade required banner
}

Type guard

function isSemver(v: string): v is string {
  return typeof v === 'string' && semver.valid(v) !== null;
}

Try / catch

try {
  await socket.connect();
} catch (e) {
  if (e?.code === 'unsupported_client_version') {
    // show 'please update' UI and stop retrying
    return showUpgradeRequired(e.requiredVersion);
  }
  throw e;
}

Prevention

When it happens

Trigger: Client connects with clientVersion undefined/empty; canary build not in an allowed canary namespace; client version below 0.26.0; non-semver string passed as clientVersion.

Common situations: Old desktop/electron client pointing at a newer server; embedded webview shipping a stale blocksuite bundle; CI hitting the gateway with a raw socket and no version header; canary build deployed outside the canary namespace.

Related errors


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