RocketChat/Rocket.Chat · error · Error
error-license-user-limit-reached
Error message
error-license-user-limit-reached
What it means
Thrown by syncUserRoles in syncUserRoles.ts:76 when promoting a former guest (existingRoles === ['guest']) to a non-guest role while License.shouldPreventAction('activeUsers') is true — i.e. the EE license's active-user seat limit would be exceeded. NOTE: plain `new Error('error-license-user-limit-reached')`.
Source
Thrown at apps/meteor/ee/server/lib/syncUserRoles.ts:76
newRoleList: Array<IRole['_id']>,
{ allowedRoles, skipRemovingRoles, scope }: setUserRolesOptions,
): Promise<void> {
const user = await Users.findOneById<Pick<IUser, '_id' | 'username' | 'roles'>>(uid, { projection: { username: 1, roles: 1 } });
if (!user) {
throw new Error('error-user-not-found');
}
const existingRoles = user.roles;
const rolesToAdd = filterRoleList(newRoleList, existingRoles, allowedRoles);
const rolesToRemove = filterRoleList(existingRoles, newRoleList, allowedRoles);
if (!rolesToAdd.length && !rolesToRemove.length) {
return;
}
const wasGuest = existingRoles.length === 1 && existingRoles[0] === 'guest';
if (wasGuest && (await License.shouldPreventAction('activeUsers'))) {
throw new Error('error-license-user-limit-reached');
}
if (rolesToAdd.length && (await addUserRolesAsync(uid, rolesToAdd, scope))) {
broadcastRoleChange('added', rolesToAdd, user);
}
if (skipRemovingRoles || !rolesToRemove.length) {
return;
}
if (await removeUserFromRolesAsync(uid, rolesToRemove, scope)) {
broadcastRoleChange('removed', rolesToRemove, user);
}
}
View on GitHub (pinned to f9d3ec372b)
Solutions
- Free an active-user seat (deactivate or demote another user to guest) before promoting.
- Upgrade the license tier to raise the activeUsers cap.
- Pre-check License.shouldPreventAction('activeUsers') and the guest state before calling syncUserRoles.
Example fix
// before
await syncUserRoles(uid, ['agent'], opts);
// after
const u = await Users.findOneById(uid, { projection: { roles: 1 } });
const wasGuest = u?.roles?.length === 1 && u.roles[0] === 'guest';
if (wasGuest && (await License.shouldPreventAction('activeUsers'))) {
throw new Error('no free seats; upgrade license or free a seat');
}
await syncUserRoles(uid, ['agent'], opts); Defensive patterns
Strategy: validation
Validate before calling
const u = await Users.findOneById(uid, { projection: { roles: 1 } });
const wasGuest = u?.roles?.length === 1 && u.roles[0] === 'guest';
if (wasGuest && (await License.shouldPreventAction('activeUsers'))) {
throw new Error('no free seats');
} Type guard
const isGuestOnly = (roles: string[] | undefined) => Array.isArray(roles) && roles.length === 1 && roles[0] === 'guest';
Try / catch
try { await syncUserRoles(uid, newRoles, opts); }
catch (e) {
if (e instanceof Error && e.message === 'error-license-user-limit-reached') { /* free a seat */ return; }
throw e;
} Prevention
- Check active-user seats before promoting guests in bulk.
- Surface current seat usage in the role-assignment UI.
When it happens
Trigger: Assigning any non-guest role to a user whose only current role is 'guest', on a licensed instance already at its active-user cap. The check only fires on the guest→active transition (wasGuest).
Common situations: Trial/SE license at seat capacity; bulk role assignment that promotes many guests; admin assigns 'agent'/'admin' to a guest without checking seats.
Related errors
- error-action-not-allowed
- error-max-guests-number-reached
- error-license-user-limit-reached
- error-action-not-allowed
- error-action-not-allowed
AI-assisted analysis of RocketChat/Rocket.Chat@f9d3ec372b (2026-08-12).
Data as JSON: /api/errors/68715a8e2033f161.
Report an issue: GitHub.