toeverything/AFFiNE · error · WorkspaceLicenseAlreadyExists

workspace_license_already_exists

workspace_license_already_exists

Error message

Workspace already has a license applied.

What it means

activateTeamLicense() is the transactional flow that binds a license key (obtained from the AFFiNE Pro cloud) to a workspace. It first checks the local installedLicense table for the workspace and throws WorkspaceLicenseAlreadyExists if one is already bound. The guard keeps the one-license-per-workspace invariant before contacting the remote license service.

Source

Thrown at packages/backend/server/src/plugins/license/service.ts:195

      workspaceId: this.licenseWorkspaceId(resolved),
      plan: SubscriptionPlan.SelfHostedTeam,
      recurring: this.licenseRecurring(resolved),
      quantity: this.licenseQuantity(resolved),
      issuedAt: new Date(resolved.issuedAt ?? ''),
      expiresAt,
      endAt: expiresAt,
      entity: resolved.entity ?? '',
      issuer: resolved.issuer ?? '',
      valid: true,
    };
  }

  @Transactional()
  async activateTeamLicense(workspaceId: string, licenseKey: string) {
    const installedLicense = await this.getLicense(workspaceId);

    if (installedLicense) {
      throw new WorkspaceLicenseAlreadyExists();
    }
    const occupiedLicense = await this.db.installedLicense.findUnique({
      where: { key: licenseKey },
    });
    if (occupiedLicense) {
      throw new WorkspaceLicenseAlreadyExists();
    }

    const data = this.remoteLicense(
      await licenseClient.activate({ licenseKey })
    );

    const validatedAt = new Date();
    const expiresAt = new Date(data.expiresAt);

    const installed = await this.db.installedLicense.create({
      data: {
        workspaceId,

View on GitHub (pinned to b4c8548c09)

Solutions

  1. Check for an existing license first: call getLicense(workspaceId) (or the admin settings UI) and remove it with removeTeamLicense(workspaceId) before activating.
  2. If this was a duplicate submit, ignore the error — the workspace already has the intended license; verify via getLicense that the installed key matches.
  3. In UIs, disable the activate button while a request is in flight to prevent double activation.
Defensive patterns

Strategy: validation

Validate before calling

const existing = await licenseService.getLicense(workspaceId);
if (existing) {
  // already activated — remove first if switching: await licenseService.removeTeamLicense(workspaceId);
} else {
  await licenseService.activateTeamLicense(workspaceId, licenseKey);
}

Type guard

function isWorkspaceLicenseAlreadyExists(e: unknown): e is WorkspaceLicenseAlreadyExists {
  return e instanceof WorkspaceLicenseAlreadyExists;
}

Try / catch

try {
  await licenseService.activateTeamLicense(workspaceId, licenseKey);
} catch (e) {
  if (e instanceof WorkspaceLicenseAlreadyExists) {
    const current = await licenseService.getLicense(workspaceId);
    if (current?.key !== licenseKey) throw new Error('Remove the existing license before activating a new one');
    // else: idempotent success — the intended key is already active
  } else throw e;
}

Prevention

When it happens

Trigger: Calling activateTeamLicense(workspaceId, licenseKey) when getLicense(workspaceId) already returns a row — e.g. double activation, retrying an activation that actually succeeded, or activating a second license without removing the first.

Common situations: Frontend double-submit or retry after a timeout where the first request committed; admin switching plans and forgetting to call removeTeamLicense first; race between two admins activating different keys simultaneously.

Related errors


AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18). Data as JSON: /api/errors/3a19c642ac83e022. Report an issue: GitHub.