fullstackhero/dotnet-starter-kit · error · NotFoundException

Provisioning not found for tenant

Error message

Provisioning not found for tenant {tenantId}.

What it means

GetStatusAsync loads the most recent TenantProvisioning record for the tenant and throws NotFoundException when none exists. It means provisioning has never been started for this tenant, so there is no status to report. The exception maps to a 404-style response.

Solutions

  1. Start provisioning first (StartAsync) before querying status.
  2. Verify the tenantId is correct and the tenant exists (Tenant {id} not found would otherwise surface).
  3. Check the API/DbMigrator is connected to the correct database/environment where provisioning ran.
  4. Handle the NotFound case in the client: treat it as 'never provisioned' and show a start-provisioning action instead of retrying.

Example fix

// before
var status = await provisioningService.GetStatusAsync(tenantId, ct);

// after
var tenant = await tenantService.GetAsync(tenantId, ct); // throws if tenant missing
var provisioning = await db.Set<TenantProvisioning>()
    .FirstOrDefaultAsync(p => p.TenantId == tenantId, ct);
if (provisioning is null)
{
    await provisioningService.StartAsync(tenantId, ct);
    provisioning = await provisioningService.GetStatusAsync(tenantId, ct);
}
Defensive patterns

Strategy: validation

Validate before calling

var provisioningExists = await db.Set<TenantProvisioning>().AnyAsync(p => p.TenantId == tenantId, ct);
if (!provisioningExists) await provisioningService.StartAsync(tenantId, ct);

Type guard

bool HasProvisioning(TenantProvisioningStatusDto? s) => s is not null;

Try / catch

try
{
    var status = await provisioningService.GetStatusAsync(tenantId, ct);
}
catch (NotFoundException)
{
    // never provisioned — start it instead of polling forever
    await provisioningService.StartAsync(tenantId, ct);
}

Prevention

When it happens

Trigger: Calling GetStatusAsync for a tenantId that never had StartAsync invoked; passing a misspelled or stale tenant id; querying status after the provisioning rows were purged; checking status on the wrong environment/database.

Common situations: Dashboard polling status immediately after tenant creation but before provisioning starts; typo'd tenant id in a script; pointing the API at a different database than where provisioning ran; data cleanup job removed old provisioning records.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15). Data as JSON: /api/errors/1e780cd044b78a47. Report an issue: GitHub.

Appendix: source

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

        await _dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);

        return provisioning;
    }

    public async Task<TenantProvisioning?> GetLatestAsync(string tenantId, CancellationToken cancellationToken)
    {
        return await _dbContext.Set<TenantProvisioning>()
            .Include(p => p.Steps)
            .Where(p => p.TenantId == tenantId)
            .OrderByDescending(p => p.CreatedUtc)
            .FirstOrDefaultAsync(cancellationToken)
            .ConfigureAwait(false);
    }

    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}.");
        }
    }

View on GitHub (pinned to 3f2959e683)