elsa-workflows/elsa-core · error · DbUpdateException

Failed to insert AI proposal

Error message

Failed to insert AI proposal {proposal.Id}, and no existing record was found for retry.

What it means

When the initial insert in SaveAsync fails with a concurrency/key exception, the store retries as an update (RetryAsUpdateAsync). It re-fetches the proposal record with ChangeTracker cleared; if the record no longer exists (deleted between the failed insert and the retry), it wraps the situation in a new DbUpdateException with this message, preserving the original exception as InnerException.

Solutions

  1. Check originalException.InnerException to find the real insert failure (unique constraint vs. connection).
  2. Add a retry policy around SaveAsync; if the record was concurrently deleted, decide whether to re-insert or surface a not-found error.
  3. Remove conflicting unique indexes that treat legitimate new proposals as duplicates.
  4. Log and investigate concurrent writers to the same proposal Id.

Example fix

// before
await proposalStore.SaveAsync(proposal); // DbUpdateException wrapped: "Failed to insert..."
// after
try { await proposalStore.SaveAsync(proposal); }
catch (DbUpdateException ex) when (ex.InnerException is DbUpdateConcurrencyException) { /* re-fetch and decide */ }
Defensive patterns

Strategy: retry

Validate before calling

var exists = await dbContext.Proposals.AnyAsync(p => p.Id == proposal.Id);
// decide: if not exists after failed insert, re-insert or surface a permanent error

Try / catch

try { await proposalStore.SaveAsync(proposal); }
catch (DbUpdateException ex) { logger.LogWarning(ex, "Insert retry failed for proposal {Id}", proposal.Id); /* check ex.InnerException; re-fetch and re-insert or fail */ }

Prevention

When it happens

Trigger: SaveAsync hits a DbUpdateException on insert, RetryAsUpdateAsync runs FindAsync on Proposals, and the record is not found — i.e., the original insert failed on a unique constraint other than the primary key, or the row was deleted concurrently.

Common situations: Concurrent deletion of the proposal between insert failure and retry; a unique-index violation on a non-PK column (e.g. conversation/sequence key) causing insert failure where no row with that Id exists; connection/transaction issues making the fetched row invisible.

Related errors


AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13). Data as JSON: /api/errors/c2e4cf4c41b80587. Report an issue: GitHub.

Appendix: source

Thrown at src/modules/Elsa.AI.Persistence.EFCore/Stores/EFCoreAIProposalStore.cs:58

        Map(proposal, record);

        try
        {
            await dbContext.SaveChangesAsync(cancellationToken);
        }
        catch (DbUpdateException e) when (isNew)
        {
            await RetryAsUpdateAsync(proposal, e, cancellationToken);
        }
    }

    private async ValueTask RetryAsUpdateAsync(AIProposal proposal, DbUpdateException originalException, CancellationToken cancellationToken)
    {
        dbContext.ChangeTracker.Clear();
        var record = await dbContext.Proposals.FindAsync([proposal.Id], cancellationToken);
        if (record == null)
            throw new DbUpdateException($"Failed to insert AI proposal {proposal.Id}, and no existing record was found for retry.", originalException);

        if (!BelongsToTenant(record.TenantId, proposal.TenantId))
            throw new InvalidOperationException("Cannot overwrite an AI proposal that belongs to another tenant.");

        ValidateUserOwnership(record, proposal);

        Map(proposal, record);
        await dbContext.SaveChangesAsync(cancellationToken);
    }

    private static AIProposal Map(AIProposalRecord record) =>
        new()
        {
            Id = record.Id,
            TenantId = record.TenantId,
            ConversationId = record.ConversationId,
            Kind = ParseEnum(record.Kind, AIProposalKind.WorkflowCreate),
            Status = ParseEnum(record.Status, AIProposalStatus.Draft),

View on GitHub (pinned to fe9217bdfa)