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
- Promote another user to admin first: POST /api/v1/roles.addUserToRole with roleId 'admin'
- Verify with GET /api/v1/users.list?query={"roles":"admin"} that total is greater than 1 before removing
- If this admin is the one being kept, remove a different admin instead
- 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
- Seed every workspace with at least two admins before running role automation
- Add an admin-count precheck to offboarding scripts
- Never bulk-strip roles without excluding the final admin
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
- error-action-not-allowed
- error-id-param-not-provided
- error-invalid-param
- error-invalid-permission
- error-invalid-role
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)