fullstackhero/dotnet-starter-kit · warning · CustomException
tenant is already deactivated
Error message
tenant {id} is already deactivated What it means
TenantService.DeactivateAsync throws a CustomException when the tenant is already inactive (IsActive == false), because deactivating an inactive tenant is a redundant state change. The guard is the first check in the method, before the active-tenant-count and root-tenant protections.
Solutions
- Check tenant.IsActive before calling DeactivateAsync and skip when already inactive.
- Catch CustomException and treat as success in idempotent batch jobs.
- If a later step in your pipeline failed after deactivation, resume from the failed step rather than re-deactivating.
- Use return message 'tenant {id} is now deactivated' from the first call as the signal it already happened.
Example fix
// before
tenants.ForEach(id => tenantService.DeactivateAsync(id, ct).Wait());
// after
foreach (var id in tenants)
{
var t = await tenantService.GetAsync(id, ct);
if (t.IsActive) await tenantService.DeactivateAsync(id, ct);
} Defensive patterns
Strategy: validation
Validate before calling
var tenant = await tenantService.GetAsync(id, ct); if (!tenant.IsActive) continue; // already inactive
Type guard
bool NeedsDeactivation(AppTenantInfo t) => t.IsActive;
Try / catch
try
{
await tenantService.DeactivateAsync(id, ct);
}
catch (CustomException ex) when (ex.Message.Contains("already deactivated"))
{
// idempotent success
} Prevention
- Check IsActive before deactivating, especially in batch scripts.
- Treat already-deactivated as success in pipelines.
- Resume failed pipelines from the failed step, not from the start.
- Guard UI buttons based on current tenant state.
When it happens
Trigger: Calling DeactivateAsync on a tenant with IsActive == false; retrying a deactivation that already succeeded; two concurrent deactivation requests where the second arrives after the first persisted.
Common situations: Double-clicking 'Deactivate'; batch scripts deactivating a list that contains already-inactive tenants; re-running a failed pipeline whose deactivation step succeeded but a later step failed.
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
- tenant is already activated
- At least one active tenant is required.
- Not Found.
- Storage quota exceeded
- Subscription cannot be backdated.
AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15).
Data as JSON: /api/errors/b6be8b961fcd8ec9.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Multitenancy/Modules.Multitenancy/Services/TenantService.cs:126
public async Task SeedTenantAsync(AppTenantInfo tenant, CancellationToken cancellationToken)
{
using var scope = _serviceProvider.CreateScope();
scope.ServiceProvider.GetRequiredService<IMultiTenantContextSetter>()
.MultiTenantContext = new MultiTenantContext<AppTenantInfo>(tenant);
foreach (var initializer in scope.ServiceProvider.GetServices<IDbInitializer>())
{
await initializer.SeedAsync(cancellationToken).ConfigureAwait(false);
}
}
public async Task<string> DeactivateAsync(string id, CancellationToken cancellationToken = default)
{
var tenant = await GetTenantInfoAsync(id, cancellationToken).ConfigureAwait(false);
if (!tenant.IsActive)
{
throw new CustomException($"tenant {id} is already deactivated");
}
int tenantCount = (await _tenantStore.GetAllAsync().ConfigureAwait(false)).Count(t => t.IsActive);
if (tenantCount <= 1)
{
throw new CustomException("At least one active tenant is required.");
}
if (tenant.Id.Equals(MultitenancyConstants.Root.Id, StringComparison.OrdinalIgnoreCase))
{
throw new CustomException("The root tenant cannot be deactivated.");
}
tenant.Deactivate();
await _tenantStore.UpdateAsync(tenant).ConfigureAwait(false);
await RefreshTenantCacheAsync(tenant).ConfigureAwait(false);
return $"tenant {id} is now deactivated";
}View on GitHub (pinned to 3f2959e683)