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
- Purge stale Hangfire/jobs referencing deleted provisioning runs, or make the consumer tolerate missing runs.
- Verify the correlationId and tenantId passed to the job match an existing TenantProvisionings row.
- If the DB was reset, clear job storage (Redis/Hangfire) so orphaned jobs don't re-run.
- 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
- Clear Hangfire/Redis job storage when resetting or migrating the provisioning database.
- Don't persist correlationIds across environment restores; create fresh runs.
- Make background consumers idempotent: treat missing runs as stale and skip.
- Purge provisioning rows only when no jobs referencing them are queued.
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
- Tenant not found during provisioning.
- Tenant not found for provisioning.
- Provisioning not found for tenant
- Invoice not found.
- Invoice not found.
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)