fullstackhero/dotnet-starter-kit · error · CustomException
Tenant must retain at least one administrator.
Error message
Tenant must retain at least one administrator.
What it means
Thrown by EnsureMinimumAdminCountAsync after the pending removal is accounted for: if the tenant would end up with zero or one administrator, the change is rejected. Enforces the 'at least one active administrator' tenant invariant at the role level.
Solutions
- Promote another user to Admin before demoting the current one.
- Perform changes in an order that never drops admin count to 1.
- If the count looks wrong, verify RoleConstants.Admin matches the actual role name in the database.
- For tenant offboarding, transfer ownership first via a different admin account.
Example fix
// before: demote directly, may hit the minimum-admin guard await userRoleService.AssignRolesAsync(soleAdminId, noAdminRoles, ct); // after: promote a successor first await userRoleService.AssignRolesAsync(successorId, rolesWithAdmin, ct); await userRoleService.AssignRolesAsync(soleAdminId, noAdminRoles, ct);
Defensive patterns
Strategy: validation
Validate before calling
const admins = await apiFetch("/api/v1/users?role=Admin").then(r => r.length);
if (demotions >= admins - 1) throw new Error("Tenant must keep at least one administrator"); Try / catch
try { await assignRoles(userId, roles); } catch (e) {
if (e.status === 400 && /at least one administrator/i.test(e.message)) notify("Promote a successor admin first");
else throw e;
} Prevention
- Promote a new admin before demoting/offboarding the current one.
- Apply bulk role changes in an order that never empties admins.
- Keep RoleConstants.Admin in sync with actual role names in the DB.
When it happens
Trigger: Demoting or removing the Admin role from a user when the tenant currently has exactly one (or the last two) admins.
Common situations: Offboarding the only admin in a small tenant; bulk demotions that would exhaust admins; misconfigured role names so GetUsersInRoleAsync(Admin) undercounts (e.g. custom role constant changed).
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Administrators cannot remove their own admin role.
- operation failed
- role not found
- Role store not configured. Ensure .AddRoles
- roles not found
AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15).
Data as JSON: /api/errors/f717763faf70cea8.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Identity/Modules.Identity/Services/UserRoleService.cs:118
}
// After this removal, at least one admin must remain in the tenant — matches
// the "at least one active administrator" invariant enforced on user deactivation.
await EnsureMinimumAdminCountAsync();
}
private bool IsRootTenantAdmin(FshUser user)
{
return user.Email == MultitenancyConstants.Root.EmailAddress
&& multiTenantContextAccessor?.MultiTenantContext?.TenantInfo?.Id == MultitenancyConstants.Root.Id;
}
private async Task EnsureMinimumAdminCountAsync()
{
int adminCount = (await userManager.GetUsersInRoleAsync(RoleConstants.Admin)).Count;
if (adminCount <= 1)
{
throw new CustomException(
"Tenant must retain at least one administrator.",
Array.Empty<string>(),
HttpStatusCode.BadRequest);
}
}
private async Task<List<string>> ProcessRoleAssignmentsAsync(FshUser user, List<UserRoleDto> userRoles)
{
var assignedRoles = new List<string>();
foreach (var userRole in userRoles)
{
if (await roleManager.FindByNameAsync(userRole.RoleName!) is null)
{
continue;
}
if (userRole.Enabled)View on GitHub (pinned to 3f2959e683)