fullstackhero/dotnet-starter-kit · error · NotFoundException

Provisioning for tenant not found.

Error message

Provisioning {correlationId} for tenant {tenantId} not found.

What it means

RequireAsync is the internal loader used by provisioning operations that address a specific run by correlationId (e.g. step updates, completion). It queries TenantProvisioning by (TenantId, CorrelationId) with Steps included and throws NotFoundException when no matching row exists. A correlationId identifies one provisioning attempt, so this means that attempt record is absent.

Solutions

  1. Purge stale Hangfire/jobs referencing deleted provisioning runs, or make the consumer tolerate missing runs.
  2. Verify the correlationId and tenantId passed to the job match an existing TenantProvisionings row.
  3. If the DB was reset, clear job storage (Redis/Hangfire) so orphaned jobs don't re-run.
  4. Re-run provisioning from StartAsync to create a fresh run/correlationId instead of addressing the old one.

Example fix

// before
var provisioning = await provisioningService.RetryAsync(tenantId, ct); // uses stale correlationId from job payload

// after
// drive work from a fresh run created by StartAsync/RetryAsync rather than a persisted job payload
var run = await provisioningService.StartAsync(tenantId, ct);
// and guard consumers:
var record = await db.Set<TenantProvisioning>()
    .FirstOrDefaultAsync(p => p.TenantId == tenantId && p.CorrelationId == correlationId, ct);
if (record is null) return; // job is stale; skip instead of throwing
Defensive patterns

Strategy: try-catch

Validate before calling

var exists = await db.Set<TenantProvisioning>().AnyAsync(
    p => p.TenantId == tenantId && p.CorrelationId == correlationId, ct);
if (!exists) { /* stale job — skip or re-run provisioning */ }

Type guard

bool RunExists(TenantProvisioning? p, string correlationId) => p is not null && p.CorrelationId == correlationId;

Try / catch

try
{
    var run = await RequireAsync(tenantId, correlationId, ct);
}
catch (NotFoundException)
{
    logger.LogWarning("Stale provisioning job {CorrelationId} for {TenantId} — skipping", correlationId, tenantId);
    return; // don't fail the job on orphaned payloads
}

Prevention

When it happens

Trigger: A background job (Hangfire) resumes with a correlationId whose TenantProvisioning row was deleted or never committed; passing a correlationId from a different environment/DB; tenantId/correlationId swapped or typo'd; job storage retained records longer than the database (TryEnsureJobStorage succeeded but data purged).

Common situations: Redis/Hangfire job storage not cleared after a database reset, so old jobs fire with orphaned correlationIds; manual cleanup of TenantProvisioning rows while jobs were queued; cross-environment replay of job payloads.

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

Appendix: source

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

    {
        var provisioning = await RequireAsync(tenantId, correlationId, cancellationToken).ConfigureAwait(false);

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

        provisioning.MarkCompleted();
        await _dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
    }

    private async Task<TenantProvisioning> RequireAsync(string tenantId, string correlationId, CancellationToken cancellationToken)
    {
        return await _dbContext.Set<TenantProvisioning>()
            .Include(p => p.Steps)
            .FirstOrDefaultAsync(p => p.TenantId == tenantId && p.CorrelationId == correlationId, cancellationToken)
            .ConfigureAwait(false)
            ?? throw new NotFoundException($"Provisioning {correlationId} for tenant {tenantId} not found.");
    }

    private static bool TryEnsureJobStorage()
    {
        try
        {
            _ = JobStorage.Current;
            return true;
        }
        catch (InvalidOperationException)
        {
            return false;
        }
    }

    private async Task RunInlineProvisioningAsync(string tenantId, string correlationId, CancellationToken cancellationToken)
    {
        using var scope = _scopeFactory.CreateScope();

View on GitHub (pinned to 3f2959e683)