fullstackhero/dotnet-starter-kit · error · InvalidOperationException

Invalid Tenant

Error message

Invalid Tenant

What it means

AppTenantInfo.Activate enables a tenant (IsActive = true) but refuses to activate the root tenant, throwing InvalidOperationException("Invalid Tenant"). The root tenant is the system-level tenant and must never be switched to an active/normal state via this API.

Solutions

  1. Skip the root tenant when activating: if (tenant.Id != MultitenancyConstants.Root.Id) tenant.Activate();
  2. Filter the root tenant out of management queries/UI lists.
  3. Reject the root tenant id at the endpoint level with a clear 400 instead of letting domain code throw.
  4. Audit call sites like EnsureDemoTenantsExistAsync that loop tenants indiscriminately.

Example fix

// before
foreach (var t in tenants) t.Activate();

// after
foreach (var t in tenants.Where(t => t.Id != MultitenancyConstants.Root.Id))
    t.Activate();
Defensive patterns

Strategy: validation

Validate before calling

if (tenant.Id == MultitenancyConstants.Root.Id)
    throw new ArgumentException("The root tenant cannot be activated.");
tenant.Activate();

Type guard

bool IsActivatable(AppTenantInfo t) => t.Id != MultitenancyConstants.Root.Id && !t.IsActive;

Try / catch

try {
    tenant.Activate();
} catch (InvalidOperationException ex) when (ex.Message == "Invalid Tenant") {
    return Results.BadRequest("The root tenant cannot be activated.");
}

Prevention

When it happens

Trigger: Calling tenant.Activate() when tenant.Id equals MultitenancyConstants.Root.Id, e.g. iterating all tenants and activating each, or an admin endpoint receiving 'root' as the tenant id.

Common situations: Bulk activation scripts that don't exclude the root tenant; admin UI list that includes root; seeding or test fixtures activating the default tenant; user submitting the root tenant id in a management endpoint.

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/2ac08eba7916caf4. Report an issue: GitHub.

Appendix: source

Thrown at src/BuildingBlocks/Shared/Multitenancy/AppTenantInfo.cs:65

    /// <summary>Per-tenant quota overrides. Serialized as JSON by the tenant store; empty by default.</summary>
    public Dictionary<QuotaResource, long> QuotaLimits { get; set; } = new();

    public void AddValidity(int months) =>
        ValidUpto = ValidUpto.AddMonths(months);

    public void SetValidity(in DateTime validTill)
    {
        var normalized = validTill;
        ValidUpto = ValidUpto < normalized
            ? normalized
            : throw new InvalidOperationException("Subscription cannot be backdated.");
    }

    public void Activate()
    {
        if (Id == MultitenancyConstants.Root.Id)
        {
            throw new InvalidOperationException("Invalid Tenant");
        }

        IsActive = true;
    }

    public void Deactivate()
    {
        if (Id == MultitenancyConstants.Root.Id)
        {
            throw new InvalidOperationException("Invalid Tenant");
        }

        IsActive = false;
    }

    string? IAppTenantInfo.ConnectionString
    {
        get => ConnectionString;

View on GitHub (pinned to 3f2959e683)