elsa-workflows/elsa-core · error · InvalidOperationException

The Elsa user was deleted while its external identity link…

Error message

The Elsa user was deleted while its external identity link was being replaced.

What it means

Thrown by EnsureReplacementUserStillExistsAsync (called from ReplaceAsync). When replacing an existing external identity link with a new link/target user, the provisioner verifies the replacement user still exists; if it was deleted concurrently, it calls CompensateReplacementAsync to restore the previous link state and then throws this error. The invariant is: a link must never point at a non-existent user.

Solutions

  1. Retry the replace operation; after compensation the old link is restored and the replace can be attempted again safely.
  2. Check CompensateReplacementAsync logs to confirm the previous link was actually restored before retrying; if this error's sibling messages appear, repair links manually.
  3. Make user deletion flow through the provisioner so replacement targets are not deleted mid-operation.
  4. Add row-level locking or an existence foreign key from link to user so deletes block/queue behind replacement.

Example fix

// before
var link = await provisioner.ReplaceAsync(oldLink, replacement, cancellationToken);
// after
catch (InvalidOperationException ex) when (ex.Message.Contains("deleted while its external identity link was being replaced"))
{
    link = await provisioner.ReplaceAsync(oldLink, replacement, cancellationToken); // old link restored; retry
}
Defensive patterns

Strategy: retry

Validate before calling

bool replacementUserExists = await provisioningService.ExistsAsync(replacementUser, wasCreated: false, ct);
if (!replacementUserExists) throw new InvalidOperationException("Replacement user missing before replace");

Try / catch

try { link = await provisioner.ReplaceAsync(oldLink, replacementLink, replacementUser, ct); }
catch (InvalidOperationException ex) when (ex.Message.Contains("deleted while its external identity link was being replaced"))
{
    link = await provisioner.ReplaceAsync(oldLink, replacementLink, replacementUser, ct); // old link restored; safe retry
}

Prevention

When it happens

Trigger: ReplaceAsync writes the replacement link, then ExistsAsync(replacementUser) returns false because the replacement user row was deleted between creation and verification (concurrent admin delete, cleanup job, cascade).

Common situations: User deleted by an operator mid account-merge/re-link; a background job purging users races with an external-identity re-link operation; duplicate sign-in flows replacing each other's links.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at src/modules/Elsa.ExternalAuthentication.Persistence.EFCore/Stores/EFCoreExternalIdentityProvisioner.cs:277

        if (await _userProvisioningService.ExistsAsync(user, wasCreated, cancellationToken))
            return;

        await using var dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
        await dbContext.ExternalIdentityLinks.Where(x => x.Id == link.Id).ExecuteDeleteAsync(cancellationToken);
        throw new InvalidOperationException("The Elsa user was deleted while its external identity link was being created.");
    }

    private async ValueTask EnsureReplacementUserStillExistsAsync(
        PersistedExternalIdentityLink oldLink,
        PersistedExternalIdentityLink replacementLink,
        User replacementUser,
        CancellationToken cancellationToken)
    {
        if (await _userProvisioningService.ExistsAsync(replacementUser, false, cancellationToken))
            return;

        await CompensateReplacementAsync(oldLink, replacementLink, cancellationToken);
        throw new InvalidOperationException("The Elsa user was deleted while its external identity link was being replaced.");
    }

    private async ValueTask CompensateReplacementAsync(
        PersistedExternalIdentityLink oldLink,
        PersistedExternalIdentityLink replacementLink,
        CancellationToken cancellationToken)
    {
        var commitAttempted = false;
        try
        {
            await using var dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
            await using var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken);
            await dbContext.ExternalIdentityLinks.Where(x => x.Id == replacementLink.Id).ExecuteDeleteAsync(cancellationToken);
            dbContext.ExternalIdentityLinks.Add(new PersistedExternalIdentityLink
            {
                Id = oldLink.Id,
                TenantId = oldLink.TenantId,
                ConnectionKey = oldLink.ConnectionKey,

View on GitHub (pinned to fe9217bdfa)