{"record":{"id":"0d83c1e3934dcc0b","repo":"elsa-workflows/elsa-core","slug":"failed-to-insert-ai-conversation-conversation-id-and-no","errorCode":null,"errorMessage":"Failed to insert AI conversation {conversation.Id}, and no existing record was found for retry.","messagePattern":"Failed to insert AI conversation (.+?), and no existing record was found for retry\\.","errorType":"exception","errorClass":"DbUpdateException","httpStatus":null,"severity":"error","filePath":"src/modules/Elsa.AI.Persistence.EFCore/Stores/EFCoreAIConversationStore.cs","lineNumber":66,"sourceCode":"\n        Map(conversation, record);\n\n        try\n        {\n            await dbContext.SaveChangesAsync(cancellationToken);\n        }\n        catch (DbUpdateException e) when (isNew)\n        {\n            await RetryAsUpdateAsync(conversation, e, cancellationToken);\n        }\n    }\n\n    private async ValueTask RetryAsUpdateAsync(AIConversation conversation, DbUpdateException originalException, CancellationToken cancellationToken)\n    {\n        dbContext.ChangeTracker.Clear();\n        var record = await dbContext.Conversations.FindAsync([conversation.Id], cancellationToken);\n        if (record == null)\n            throw new DbUpdateException($\"Failed to insert AI conversation {conversation.Id}, and no existing record was found for retry.\", originalException);\n\n        if (!BelongsToTenant(record.TenantId, conversation.TenantId))\n            throw new InvalidOperationException(\"Cannot overwrite an AI conversation that belongs to another tenant.\");\n\n        ValidateUserOwnership(record, conversation);\n\n        Map(conversation, record);\n        await dbContext.SaveChangesAsync(cancellationToken);\n    }\n\n    private static AIConversation Map(AIConversationRecord record) =>\n        new()\n        {\n            Id = record.Id,\n            TenantId = record.TenantId,\n            UserId = record.UserId,\n            Title = record.Title,\n            Status = ParseEnum(record.Status, AIConversationStatus.Active),","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/modules/Elsa.AI.Persistence.EFCore/Stores/EFCoreAIConversationStore.cs#L48-L84","documentation":"When an insert fails with DbUpdateException (typically a concurrent insert of the same conversation Id), SaveAsync retries as an update by re-fetching the record. If the record is still not found, RetryAsUpdateAsync throws a DbUpdateException wrapping the original exception, signaling that neither insert nor an updateable record succeeded.","triggerScenarios":"Concurrent SaveAsync calls racing on the same conversation Id: the first insert fails on a unique-constraint, but the retry's FindAsync returns null (row deleted or transaction not yet committed).","commonSituations":"Duplicate message handling / concurrent AI turns saving the same conversation; retry storms after a transient DB failure; the conversation being deleted between the failed insert and the retry.","solutions":["Avoid concurrent saves for the same conversation Id (serialize per conversation).","Inspect the inner exception (originalException) for the actual DB error.","Retry the save once more after the concurrent transaction commits.","Delete-and-recreate the conversation if the record was intentionally removed."],"exampleFix":"// before\n// concurrent tasks saving the same conversation id\nawait Task.WhenAll(task1, task2); // one fails insert, retry finds nothing\n// after\n// serialize saves per conversation\nawait conversationLocks.Get(id).WaitAsync();\ntry { await store.SaveAsync(conversation); } finally { conversationLocks.Release(id); }","handlingStrategy":"retry","validationCode":"var exists = await dbContext.Conversations.AnyAsync(c => c.Id == conversation.Id);\n// serialize saves for the same conversation id before calling SaveAsync","typeGuard":null,"tryCatchPattern":"try { await store.SaveAsync(conversation); }\ncatch (DbUpdateException ex) when (ex.InnerException is not null && ex.Message.Contains(\"no existing record was found\"))\n{ logger.LogWarning(ex, \"Lost race saving conversation {Id}; retrying\", conversation.Id); await Task.Delay(50); await store.SaveAsync(conversation); }","preventionTips":["Serialize SaveAsync calls per conversation Id (per-conversation lock/queue).","Avoid deleting conversations while concurrent saves may be in flight.","Include the inner DbUpdateException when logging for diagnosis.","Add concurrency tests that save the same conversation Id in parallel."],"tags":["dotnet","ef-core","concurrency","database"],"backgroundTag":"database-write-failed","analyzedSha":"fe9217bdfa0e27f0e09e45006eb6898f616e513d","analyzedAt":"2026-09-13T20:32:34.702Z","contentChangedAt":"2026-09-13T20:32:34.702Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}