fullstackhero/dotnet-starter-kit · error · CustomException

Tenant must have at least one active administrator.

Error message

Tenant must have at least one active administrator.

What it means

UserStatusService.EnsureMinimumActiveAdminsAsync throws CustomException with 400 BadRequest ("Tenant must have at least one active administrator.") when deactivating the target would leave the tenant with zero active admin users. It fetches all users in the Admin role and verifies at least one remains IsActive; audited as NoActiveAdmins first.

Solutions

  1. Activate another user and grant them the Admin role before deactivating this one.
  2. Reactivate an existing inactive admin first.
  3. Skip the last active admin in bulk deactivation jobs.
  4. Keep at least two active admins per tenant as an operational rule.

Example fix

// before
await mediator.Send(new ToggleUserStatusCommand { UserId = lastAdminId, ActivateUser = false });
// after
var newAdmin = await userManager.FindByEmailAsync("ops@tenant.com");
await userManager.AddToRoleAsync(newAdmin, RoleConstants.Admin);
await mediator.Send(new ToggleUserStatusCommand { UserId = lastAdminId, ActivateUser = false });
Defensive patterns

Strategy: validation

Validate before calling

var admins = await userManager.GetUsersInRoleAsync(RoleConstants.Admin);
if (!admins.Any(u => u.IsActive && u.Id != targetId))
    return Result.BadRequest("Promote another active admin first.");

Try / catch

try { ... }
catch (CustomException) { showBanner("Tenant needs at least one active administrator."); }

Prevention

When it happens

Trigger: Deactivating the last remaining active administrator of a tenant, i.e. after this change no user in the Admin role would remain IsActive.

Common situations: Tenants where other admins are already inactive or their Admin role was stripped; bulk deactivation jobs that would drain all admins; freshly created tenants with a single seeded admin.

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


AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15). Data as JSON: /api/errors/73a720ef3493c4d7. Report an issue: GitHub.

Appendix: source

Thrown at src/Modules/Identity/Modules.Identity/Services/UserStatusService.cs:110

            await AuditPolicyFailureAsync(context, "AdminDeactivationBlocked", cancellationToken);
            throw new CustomException("Administrators cannot be deactivated.", Array.Empty<string>(), HttpStatusCode.BadRequest);
        }

        if (!context.ActivateUser)
        {
            await EnsureMinimumActiveAdminsAsync(context, cancellationToken);
        }
    }

    private async Task EnsureMinimumActiveAdminsAsync(
        ToggleStatusContext context,
        CancellationToken cancellationToken)
    {
        var activeAdmins = await userManager.GetUsersInRoleAsync(RoleConstants.Admin);
        if (!activeAdmins.Any(u => u.IsActive))
        {
            await AuditPolicyFailureAsync(context, "NoActiveAdmins", cancellationToken);
            throw new CustomException("Tenant must have at least one active administrator.", Array.Empty<string>(), HttpStatusCode.BadRequest);
        }
    }

    private static void ApplyStatusChange(ToggleStatusContext context)
    {
        if (context.ActivateUser)
        {
            context.TargetUser.Activate(context.ActorId.ToString(), context.TenantId);
        }
        else
        {
            context.TargetUser.Deactivate(context.ActorId.ToString(), "Status toggled by administrator", context.TenantId);
        }
    }

    private async Task SaveAndAuditAsync(
        ToggleStatusContext context,
        CancellationToken cancellationToken)

View on GitHub (pinned to 3f2959e683)