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

  1. Free an active-user seat (deactivate or demote another user to guest) before promoting.
  2. Upgrade the license tier to raise the activeUsers cap.
  3. 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

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


AI-assisted analysis of RocketChat/Rocket.Chat@f9d3ec372b (2026-08-12). Data as JSON: /api/errors/68715a8e2033f161. Report an issue: GitHub.