{"record":{"id":"d63cd99fbb2f1394","repo":"elsa-workflows/elsa-core","slug":"user-task-task-id-was-not-found","errorCode":null,"errorMessage":"User task '{task.Id}' was not found.","messagePattern":"User task '(.+?)' was not found\\.","errorType":"exception","errorClass":"KeyNotFoundException","httpStatus":404,"severity":"error","filePath":"src/modules/Elsa.UserTasks.Persistence.VNext/Repositories/VNextUserTaskRepository.cs","lineNumber":72,"sourceCode":"\n    public async Task<(UserTask Task, UserTaskInvitation Invitation)?> FindByInvitationTokenHashAsync(string tokenHash, CancellationToken cancellationToken = default)\n    {\n        // The document store has no cross-tenant secondary index for invitation hashes, so this scans the\n        // storage unit. Verification is rate limited and rare; correctness matters more than the scan here.\n        foreach (var task in await LoadAllAsync(cancellationToken))\n        {\n            var invitation = task.Invitations.FirstOrDefault(x => string.Equals(x.TokenHash, tokenHash, StringComparison.Ordinal));\n            if (invitation != null)\n                return (task, invitation);\n        }\n        return null;\n    }\n\n    public async Task SaveAsync(UserTask task, int expectedRevision, CancellationToken cancellationToken = default)\n    {\n        var existing = await LoadDocumentAsync(task.TenantId, task.Id, cancellationToken);\n        if (existing is null)\n            throw new KeyNotFoundException($\"User task '{task.Id}' was not found.\");\n        var loaded = existing.Value;\n        if (loaded.Task.Revision != expectedRevision)\n            throw new UserTaskRevisionConflictException(task.Id, expectedRevision);\n\n        task.Revision = expectedRevision + 1;\n        task.UpdatedAt = DateTimeOffset.UtcNow;\n        try\n        {\n            await documentStore.SaveAsync(CreateRequest(task, loaded.Document.Version), cancellationToken);\n        }\n        catch (DocumentStoreConcurrencyException exception)\n        {\n            // A writer won the race between the read above and this save. Same contract as the other\n            // providers so the caller sees one exception type regardless of the installed store.\n            throw new UserTaskRevisionConflictException(task.Id, expectedRevision, exception);\n        }\n    }\n","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/modules/Elsa.UserTasks.Persistence.VNext/Repositories/VNextUserTaskRepository.cs#L54-L90","documentation":"VNextUserTaskRepository.SaveAsync performs a load-modify-save against the document store. If LoadDocumentAsync finds no stored document for the task's tenant and id, SaveAsync throws KeyNotFoundException because updates are only valid for existing tasks. Missing documents are treated as update-of-nonexistent-entity rather than an implicit insert.","triggerScenarios":"Calling SaveAsync(task, expectedRevision) for a UserTask whose document (tenantId:taskId) was never created via a create/insert path, or was deleted beforehand.","commonSituations":"Replaying events or migrating data where the create step was skipped; task id mismatch (e.g., using a correlation id instead of the task id); cross-tenant saves where the document lives under a different tenant.","solutions":["Create the user task via the repository's create/insert API before calling SaveAsync.","Verify task.Id and task.TenantId correspond to an existing document.","Catch KeyNotFoundException and surface a not-found error, or implement upsert logic if insert-on-save is intended."],"exampleFix":"// before\nvar task = new UserTask { Id = taskId, TenantId = tenantId, Title = \"Review\" };\nawait repo.SaveAsync(task, expectedRevision: 0, ct); // throws if not created first\n// after\nvar created = await repo.CreateAsync(task, ct); // insert path first\nawait repo.SaveAsync(created, created.Revision, ct);","handlingStrategy":"try-catch","validationCode":"var exists = await repo.GetAsync(task.TenantId, task.Id, ct);\nif (exists is null) throw new InvalidOperationException(\"Create the task before saving updates.\");","typeGuard":null,"tryCatchPattern":"try\n{\n    await repo.SaveAsync(task, expectedRevision, ct);\n}\ncatch (KeyNotFoundException)\n{\n    logger.LogWarning(\"User task {TaskId} not found in tenant {TenantId}; skipping save.\", task.Id, task.TenantId);\n}","preventionTips":["Use the repository's create/insert API before the first SaveAsync.","Keep task.Id and TenantId from the originally created instance, not rebuilt values.","For upsert semantics, check existence first and branch to create."],"tags":["usertasks","document-store","concurrency"],"backgroundTag":"record-not-found","analyzedSha":"fe9217bdfa0e27f0e09e45006eb6898f616e513d","analyzedAt":"2026-09-13T20:32:34.702Z","contentChangedAt":"2026-09-13T20:32:34.702Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}