{"record":{"id":"02c3e36ec2a4b11b","repo":"RocketChat/Rocket.Chat","slug":"error-admin-required","errorCode":"error-admin-required","errorMessage":"You need to have at least one admin","messagePattern":"You need to have at least one admin","errorType":"exception","errorClass":"Meteor.Error","httpStatus":400,"severity":"error","filePath":"apps/meteor/server/api/v1/roles.ts","lineNumber":303,"sourceCode":"\n\t\t\tif (!user) {\n\t\t\t\tthrow new Meteor.Error('error-invalid-user', 'There is no user with this username');\n\t\t\t}\n\n\t\t\tconst role = await Roles.findOneById(roleId);\n\n\t\t\tif (!role) {\n\t\t\t\tthrow new Meteor.Error('error-invalid-roleId', 'This role does not exist');\n\t\t\t}\n\n\t\t\tif (!(await hasAnyRoleAsync(user._id, [role._id], scope))) {\n\t\t\t\tthrow new Meteor.Error('error-user-not-in-role', 'User is not in this role');\n\t\t\t}\n\n\t\t\tif (role._id === 'admin') {\n\t\t\t\tconst adminCount = await Roles.countUsersInRole('admin');\n\t\t\t\tif (adminCount === 1) {\n\t\t\t\t\tthrow new Meteor.Error('error-admin-required', 'You need to have at least one admin');\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tawait removeUserFromRolesAsync(user._id, [role._id], scope);\n\n\t\t\tif (settings.get('UI_DisplayRoles')) {\n\t\t\t\tvoid api.broadcast('user.roleUpdate', {\n\t\t\t\t\ttype: 'removed',\n\t\t\t\t\t_id: role._id,\n\t\t\t\t\tu: {\n\t\t\t\t\t\t_id: user._id,\n\t\t\t\t\t\tusername: user.username,\n\t\t\t\t\t},\n\t\t\t\t\tscope,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn API.v1.success({","sourceCodeStart":285,"sourceCodeEnd":321,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/api/v1/roles.ts#L285-L321","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nawait sdk.post('roles.removeUserFromRole', { roleId: 'admin', username: lastAdmin });\n\n// after\nconst { total } = await sdk.get('users.list', { query: JSON.stringify({ roles: 'admin' }) });\nif (total <= 1) {\n  await sdk.post('roles.addUserToRole', { roleId: 'admin', username: backupAdmin });\n}\nawait sdk.post('roles.removeUserFromRole', { roleId: 'admin', username: lastAdmin });","handlingStrategy":"validation","validationCode":"if (roleId === 'admin') {\n  const { total } = await sdk.get('users.list', { query: JSON.stringify({ roles: 'admin' }) });\n  if (total <= 1) throw new Error('promote another admin before demoting the last one');\n}","typeGuard":null,"tryCatchPattern":"try {\n  await sdk.post('roles.removeUserFromRole', { roleId: 'admin', username });\n} catch (e: any) {\n  if (e?.response?.data?.errorType === 'error-admin-required') {\n    await sdk.post('roles.addUserToRole', { roleId: 'admin', username: backupAdmin }); // then retry once\n    return;\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["roles","admin","lockout-prevention","rest-api"],"backgroundTag":"last-admin-protection","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","contentChangedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}