{"record":{"id":"b6be8b961fcd8ec9","repo":"fullstackhero/dotnet-starter-kit","slug":"tenant-id-is-already-deactivated","errorCode":null,"errorMessage":"tenant {id} is already deactivated","messagePattern":"tenant (.+?) is already deactivated","errorType":"exception","errorClass":"CustomException","httpStatus":400,"severity":"warning","filePath":"src/Modules/Multitenancy/Modules.Multitenancy/Services/TenantService.cs","lineNumber":126,"sourceCode":"    public async Task SeedTenantAsync(AppTenantInfo tenant, CancellationToken cancellationToken)\n    {\n        using var scope = _serviceProvider.CreateScope();\n\n        scope.ServiceProvider.GetRequiredService<IMultiTenantContextSetter>()\n            .MultiTenantContext = new MultiTenantContext<AppTenantInfo>(tenant);\n\n        foreach (var initializer in scope.ServiceProvider.GetServices<IDbInitializer>())\n        {\n            await initializer.SeedAsync(cancellationToken).ConfigureAwait(false);\n        }\n    }\n\n    public async Task<string> DeactivateAsync(string id, CancellationToken cancellationToken = default)\n    {\n        var tenant = await GetTenantInfoAsync(id, cancellationToken).ConfigureAwait(false);\n        if (!tenant.IsActive)\n        {\n            throw new CustomException($\"tenant {id} is already deactivated\");\n        }\n\n        int tenantCount = (await _tenantStore.GetAllAsync().ConfigureAwait(false)).Count(t => t.IsActive);\n        if (tenantCount <= 1)\n        {\n            throw new CustomException(\"At least one active tenant is required.\");\n        }\n\n        if (tenant.Id.Equals(MultitenancyConstants.Root.Id, StringComparison.OrdinalIgnoreCase))\n        {\n            throw new CustomException(\"The root tenant cannot be deactivated.\");\n        }\n\n        tenant.Deactivate();\n        await _tenantStore.UpdateAsync(tenant).ConfigureAwait(false);\n        await RefreshTenantCacheAsync(tenant).ConfigureAwait(false);\n        return $\"tenant {id} is now deactivated\";\n    }","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Multitenancy/Modules.Multitenancy/Services/TenantService.cs#L108-L144","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\ntenants.ForEach(id => tenantService.DeactivateAsync(id, ct).Wait());\n\n// after\nforeach (var id in tenants)\n{\n    var t = await tenantService.GetAsync(id, ct);\n    if (t.IsActive) await tenantService.DeactivateAsync(id, ct);\n}","handlingStrategy":"validation","validationCode":"var tenant = await tenantService.GetAsync(id, ct);\nif (!tenant.IsActive) continue; // already inactive","typeGuard":"bool NeedsDeactivation(AppTenantInfo t) => t.IsActive;","tryCatchPattern":"try\n{\n    await tenantService.DeactivateAsync(id, ct);\n}\ncatch (CustomException ex) when (ex.Message.Contains(\"already deactivated\"))\n{\n    // idempotent success\n}","preventionTips":["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."],"tags":["multitenancy","tenant","idempotency"],"backgroundTag":"invalid-state-transition","analyzedSha":"3f2959e683e9f83f13e55e1678c9119f63c7e8e5","analyzedAt":"2026-09-15T22:20:53.684Z","contentChangedAt":"2026-09-15T22:20:53.684Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}