RocketChat/Rocket.Chat · error · Meteor.Error

error-admin-required

error-admin-required

Error message

You need to have at least one admin

What it means

Thrown by POST roles.removeUserFromRole when removing the 'admin' role while Roles.countUsersInRole('admin') equals 1. The server refuses to strip the workspace's last admin to prevent total lockout. Note the count is global, so the error fires even when the scope parameter limits the removal to a single room.

Solutions

  1. Promote another user to admin first: POST /api/v1/roles.addUserToRole with roleId 'admin'
  2. Verify with GET /api/v1/users.list?query={"roles":"admin"} that total is greater than 1 before removing
  3. If this admin is the one being kept, remove a different admin instead
  4. As last resort for a locked-out workspace, use the CLI or database to grant admin directly

Example fix

// before
await sdk.post('roles.removeUserFromRole', { roleId: 'admin', username: lastAdmin });

// after
const { total } = await sdk.get('users.list', { query: JSON.stringify({ roles: 'admin' }) });
if (total <= 1) {
  await sdk.post('roles.addUserToRole', { roleId: 'admin', username: backupAdmin });
}
await sdk.post('roles.removeUserFromRole', { roleId: 'admin', username: lastAdmin });
Defensive patterns

Strategy: validation

Validate before calling

if (roleId === 'admin') {
  const { total } = await sdk.get('users.list', { query: JSON.stringify({ roles: 'admin' }) });
  if (total <= 1) throw new Error('promote another admin before demoting the last one');
}

Try / catch

try {
  await sdk.post('roles.removeUserFromRole', { roleId: 'admin', username });
} catch (e: any) {
  if (e?.response?.data?.errorType === 'error-admin-required') {
    await sdk.post('roles.addUserToRole', { roleId: 'admin', username: backupAdmin }); // then retry once
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: POST /api/v1/roles.removeUserFromRole with roleId 'admin' for the only remaining admin account: demoting a solo admin during offboarding, cleaning up a workspace with one admin left, or removing an admin's room-scoped admin grant while they are the sole global admin holder of the count.

Common situations: Offboarding scripts that strip all roles from the last active admin; workspaces where other admins were deleted or demoted earlier; staging servers with a single seeded admin; automated role-cleanup jobs run after team shrinkage.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18). Data as JSON: /api/errors/02c3e36ec2a4b11b. Report an issue: GitHub.

Appendix: source

Thrown at apps/meteor/server/api/v1/roles.ts:303

			if (!user) {
				throw new Meteor.Error('error-invalid-user', 'There is no user with this username');
			}

			const role = await Roles.findOneById(roleId);

			if (!role) {
				throw new Meteor.Error('error-invalid-roleId', 'This role does not exist');
			}

			if (!(await hasAnyRoleAsync(user._id, [role._id], scope))) {
				throw new Meteor.Error('error-user-not-in-role', 'User is not in this role');
			}

			if (role._id === 'admin') {
				const adminCount = await Roles.countUsersInRole('admin');
				if (adminCount === 1) {
					throw new Meteor.Error('error-admin-required', 'You need to have at least one admin');
				}
			}

			await removeUserFromRolesAsync(user._id, [role._id], scope);

			if (settings.get('UI_DisplayRoles')) {
				void api.broadcast('user.roleUpdate', {
					type: 'removed',
					_id: role._id,
					u: {
						_id: user._id,
						username: user.username,
					},
					scope,
				});
			}

			return API.v1.success({

View on GitHub (pinned to b2c16d5842)