RocketChat/Rocket.Chat · error · Error
error-license-user-limit-reached
Error message
error-license-user-limit-reached
What it means
Before applying role changes, syncUserRoles detects a pure-guest user (existingRoles.length === 1 && existingRoles[0] === 'guest') and calls License.shouldPreventAction('activeUsers'); if the Enterprise license's active-user seats are exhausted it throws Error('error-license-user-limit-reached'). Adding any role to a guest converts them into an active user - the licensed quantity - so the operation is blocked before any write. Non-guest users never hit this branch.
Solutions
- Free active seats first: deactivate or remove inactive users, then retry the role change.
- Increase the license's active-user allowance.
- Queue guest promotions and process them as seats free up; pre-check License.shouldPreventAction('activeUsers') before attempting bulk changes.
Example fix
// before
await syncUserRoles(guestUid, ['user'], opts); // throws error-license-user-limit-reached at the seat ceiling
// after
if (await License.shouldPreventAction('activeUsers')) {
await setUserActiveStatus(inactiveUid, false); // free a seat first
}
await syncUserRoles(guestUid, ['user'], opts); Defensive patterns
Strategy: validation
Validate before calling
const user = await Users.findOneById(uid, { projection: { roles: 1 } });
const isGuestOnly = (user?.roles?.length ?? 0) === 1 && user?.roles?.[0] === 'guest';
if (isGuestOnly && (await License.shouldPreventAction('activeUsers'))) {
// promoting this guest would exceed active-user seats; block before calling syncUserRoles
} Try / catch
try {
await syncUserRoles(uid, newRoleList, opts);
} catch (e: any) {
if (e?.message === 'error-license-user-limit-reached') { surface('Free a seat (deactivate a user) or raise the license limit'); return; }
throw e;
} Prevention
- Track active-user count against the license before bulk role promotions.
- Monitor seat utilization and clean up inactive users proactively.
- Remember the gate only applies to guest-only users gaining their first additional role.
When it happens
Trigger: Promoting a guest-only user to 'user', agent, or any additional role via syncUserRoles on a workspace already at its licensed active-user limit. The check is wasGuest-gated, so role changes for already-active users proceed normally.
Common situations: Bulk-invite or SSO auto-role assignment pushing active users past the seat count; guest-heavy community workspaces promoting users after a license downgrade; expired trials reverting to fewer seats than the current active-user count.
Related errors
- App could not be enabled
- error-action-not-allowed
- error-action-not-allowed
- error-duplicate-role-names-not-allowed
- error-duplicate-role-names-not-allowed
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/68715a8e2033f161.
Report an issue: GitHub.
Appendix: 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 b2c16d5842)