{"record":{"id":"807cd378f03814be","repo":"fullstackhero/dotnet-starter-kit","slug":"provisioning-correlationid-for-tenant-tenantid-not-found","errorCode":null,"errorMessage":"Provisioning {correlationId} for tenant {tenantId} not found.","messagePattern":"Provisioning (.+?) for tenant (.+?) not found\\.","errorType":"exception","errorClass":"NotFoundException","httpStatus":404,"severity":"error","filePath":"src/Modules/Multitenancy/Modules.Multitenancy/Provisioning/TenantProvisioningService.cs","lineNumber":174,"sourceCode":"    {\n        var provisioning = await RequireAsync(tenantId, correlationId, cancellationToken).ConfigureAwait(false);\n\n        if (provisioning.Status == TenantProvisioningStatus.Completed)\n        {\n            return;\n        }\n\n        provisioning.MarkCompleted();\n        await _dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);\n    }\n\n    private async Task<TenantProvisioning> RequireAsync(string tenantId, string correlationId, CancellationToken cancellationToken)\n    {\n        return await _dbContext.Set<TenantProvisioning>()\n            .Include(p => p.Steps)\n            .FirstOrDefaultAsync(p => p.TenantId == tenantId && p.CorrelationId == correlationId, cancellationToken)\n            .ConfigureAwait(false)\n            ?? throw new NotFoundException($\"Provisioning {correlationId} for tenant {tenantId} not found.\");\n    }\n\n    private static bool TryEnsureJobStorage()\n    {\n        try\n        {\n            _ = JobStorage.Current;\n            return true;\n        }\n        catch (InvalidOperationException)\n        {\n            return false;\n        }\n    }\n\n    private async Task RunInlineProvisioningAsync(string tenantId, string correlationId, CancellationToken cancellationToken)\n    {\n        using var scope = _scopeFactory.CreateScope();","sourceCodeStart":156,"sourceCodeEnd":192,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Multitenancy/Modules.Multitenancy/Provisioning/TenantProvisioningService.cs#L156-L192","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\nvar provisioning = await provisioningService.RetryAsync(tenantId, ct); // uses stale correlationId from job payload\n\n// after\n// drive work from a fresh run created by StartAsync/RetryAsync rather than a persisted job payload\nvar run = await provisioningService.StartAsync(tenantId, ct);\n// and guard consumers:\nvar record = await db.Set<TenantProvisioning>()\n    .FirstOrDefaultAsync(p => p.TenantId == tenantId && p.CorrelationId == correlationId, ct);\nif (record is null) return; // job is stale; skip instead of throwing","handlingStrategy":"try-catch","validationCode":"var exists = await db.Set<TenantProvisioning>().AnyAsync(\n    p => p.TenantId == tenantId && p.CorrelationId == correlationId, ct);\nif (!exists) { /* stale job — skip or re-run provisioning */ }","typeGuard":"bool RunExists(TenantProvisioning? p, string correlationId) => p is not null && p.CorrelationId == correlationId;","tryCatchPattern":"try\n{\n    var run = await RequireAsync(tenantId, correlationId, ct);\n}\ncatch (NotFoundException)\n{\n    logger.LogWarning(\"Stale provisioning job {CorrelationId} for {TenantId} — skipping\", correlationId, tenantId);\n    return; // don't fail the job on orphaned payloads\n}","preventionTips":["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."],"tags":["multitenancy","provisioning","not-found","hangfire"],"backgroundTag":"record-not-found","analyzedSha":"3f2959e683e9f83f13e55e1678c9119f63c7e8e5","analyzedAt":"2026-09-15T22:20:53.684Z","contentChangedAt":"2026-09-15T22:20:53.684Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}