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
- Check for an existing license first: call getLicense(workspaceId) (or the admin settings UI) and remove it with removeTeamLicense(workspaceId) before activating.
- If this was a duplicate submit, ignore the error — the workspace already has the intended license; verify via getLicense that the installed key matches.
- 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
- Fetch the current license state before showing the activation form.
- Debounce/disable the activate button while a request is in flight.
- Treat 'already exists' as idempotent success when the installed key matches.
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.