fullstackhero/dotnet-starter-kit · error · CustomException

Tenant is not provisioned. Status: .

Error message

Tenant {tenantId} is not provisioned. Status: {provisioning.Status}.

What it means

EnsureCanActivateAsync guards tenant activation: if the latest provisioning run is not Completed, activation is rejected with a CustomException stating the current provisioning status. It prevents activating tenants whose database/steps provisioning is incomplete. GetLatest is skipped only when no provisioning record exists (null short-circuits to allowed per the preceding check).

Solutions

  1. Check GetStatusAsync for the tenant and wait until Status == Completed before activating.
  2. If Status is Failed, inspect the TenantProvisioningStep records for the failing step, fix the root cause, then call RetryAsync and activate after completion.
  3. If Status is stuck Pending/Running, resolve the hung/stale job (mark Failed, retry) so a new run can complete.
  4. Sequence automation: provision -> poll until Completed -> activate.

Example fix

// before
await tenantService.ActivateAsync(tenantId, ct);

// after
var status = await provisioningService.GetStatusAsync(tenantId, ct);
if (status.Status != TenantProvisioningStatus.Completed)
    throw new InvalidOperationException($"Wait for provisioning to complete (currently {status.Status}).");
await tenantService.ActivateAsync(tenantId, ct);
Defensive patterns

Strategy: validation

Validate before calling

var status = await provisioningService.GetStatusAsync(tenantId, ct);
if (status.Status != TenantProvisioningStatus.Completed)
    throw new InvalidOperationException($"Cannot activate: provisioning status is {status.Status}.");

Type guard

bool CanActivate(TenantProvisioningStatusDto s) => s.Status == TenantProvisioningStatus.Completed;

Try / catch

try
{
    await tenantService.ActivateAsync(tenantId, ct);
}
catch (CustomException ex) when (ex.Message.Contains("is not provisioned"))
{
    var s = await provisioningService.GetStatusAsync(tenantId, ct);
    logger.LogWarning("Activation blocked for {TenantId}, provisioning status: {Status}", tenantId, s.Status);
}

Prevention

When it happens

Trigger: Calling TenantService.ActivateAsync for a tenant whose latest TenantProvisioning.Status is Pending, Running, or Failed. Also happens when a provisioning run failed partway (e.g. database step failed) and the admin tries to activate the tenant anyway.

Common situations: Provisioning job still in progress while an admin activates early; a failed migration step left status Failed; environment where Hangfire never ran the job so status remains Pending.

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

Appendix: source

Thrown at src/Modules/Multitenancy/Modules.Multitenancy/Provisioning/TenantProvisioningService.cs:103

    public async Task<TenantProvisioningStatusDto> GetStatusAsync(string tenantId, CancellationToken cancellationToken)
    {
        var provisioning = await GetLatestAsync(tenantId, cancellationToken).ConfigureAwait(false)
            ?? throw new NotFoundException($"Provisioning not found for tenant {tenantId}.");

        return ToDto(provisioning);
    }

    public async Task EnsureCanActivateAsync(string tenantId, CancellationToken cancellationToken)
    {
        var provisioning = await GetLatestAsync(tenantId, cancellationToken).ConfigureAwait(false);
        if (provisioning is null)
        {
            return;
        }

        if (provisioning.Status != TenantProvisioningStatus.Completed)
        {
            throw new CustomException($"Tenant {tenantId} is not provisioned. Status: {provisioning.Status}.");
        }
    }

    public async Task<string> RetryAsync(string tenantId, CancellationToken cancellationToken)
    {
        var provisioning = await StartAsync(tenantId, cancellationToken).ConfigureAwait(false);
        return provisioning.CorrelationId;
    }

    public async Task<bool> MarkRunningAsync(string tenantId, string correlationId, TenantProvisioningStepName step, CancellationToken cancellationToken)
    {
        var provisioning = await RequireAsync(tenantId, correlationId, cancellationToken).ConfigureAwait(false);
        var stepEntity = provisioning.Steps.First(s => s.Step == step);

        if (stepEntity.Status == TenantProvisioningStatus.Completed)
        {
            return false;
        }

View on GitHub (pinned to 3f2959e683)