{"record":{"id":"a58f101b77dfccf5","repo":"fullstackhero/dotnet-starter-kit","slug":"the-root-tenant-cannot-be-deactivated","errorCode":null,"errorMessage":"The root tenant cannot be deactivated.","messagePattern":"The root tenant cannot be deactivated\\.","errorType":"exception","errorClass":"CustomException","httpStatus":400,"severity":"error","filePath":"src/Modules/Multitenancy/Modules.Multitenancy/Services/TenantService.cs","lineNumber":137,"sourceCode":"    }\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    }\n\n    public async Task<bool> ExistsWithIdAsync(string id, CancellationToken cancellationToken = default) =>\n        await _tenantStore.GetAsync(id).ConfigureAwait(false) is not null;\n\n    public async Task<bool> ExistsWithNameAsync(string name, CancellationToken cancellationToken = default) =>\n        (await _tenantStore.GetAllAsync().ConfigureAwait(false)).Any(t => t.Name == name);\n\n    public async Task<PagedResponse<TenantDto>> GetAllAsync(GetTenantsQuery query, CancellationToken cancellationToken)\n    {\n        ArgumentNullException.ThrowIfNull(query);\n","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Multitenancy/Modules.Multitenancy/Services/TenantService.cs#L119-L155","documentation":"DeactivateAsync explicitly forbids deactivating the root tenant (MultitenancyConstants.Root.Id, compared case-insensitively). The root tenancy is the host-level tenant that other tenants depend on, so it must remain active. This check runs after the already-deactivated and active-count guards.","triggerScenarios":"Calling DeactivateAsync with the root tenant id (constant Root.Id, typically the host/default tenant); scripts iterating all tenants and deactivating each without excluding root; passing an id differing only in case from the root id.","commonSituations":"Bulk offboarding jobs that don't filter out the root tenant; admins mistaking the root tenant for a normal tenant in the admin UI; scripts that hardcode ids and accidentally include root.","solutions":["Exclude the root tenant from deactivation logic: skip when id equals MultitenancyConstants.Root.Id.","Filter bulk scripts to non-root tenants before calling DeactivateAsync.","If the root tenant should not appear as deactivatable, hide/disable the action in the UI for it.","Correct the id if you intended to deactivate a different tenant."],"exampleFix":"// before\nforeach (var t in tenants)\n    await tenantService.DeactivateAsync(t.Id, ct);\n\n// after\nforeach (var t in tenants.Where(t => !string.Equals(t.Id, MultitenancyConstants.Root.Id, StringComparison.OrdinalIgnoreCase)))\n    await tenantService.DeactivateAsync(t.Id, ct);","handlingStrategy":"validation","validationCode":"if (string.Equals(id, MultitenancyConstants.Root.Id, StringComparison.OrdinalIgnoreCase))\n    throw new InvalidOperationException(\"The root tenant cannot be deactivated.\");","typeGuard":"bool IsRootTenant(AppTenantInfo t) => t.Id.Equals(MultitenancyConstants.Root.Id, StringComparison.OrdinalIgnoreCase);","tryCatchPattern":"try\n{\n    await tenantService.DeactivateAsync(id, ct);\n}\ncatch (CustomException ex) when (ex.Message.Contains(\"root tenant cannot be deactivated\"))\n{\n    logger.LogWarning(\"Attempted to deactivate root tenant {TenantId}\", id);\n}","preventionTips":["Always exclude MultitenancyConstants.Root.Id from bulk tenant operations.","Hide the deactivate action for the root tenant in the admin UI.","Compare tenant ids case-insensitively, matching the service's OrdinalIgnoreCase check.","Never hardcode tenant id lists in scripts; derive them and filter root."],"tags":["multitenancy","root-tenant","protected-resource"],"backgroundTag":"permission-denied","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"}