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
- Retry the replace operation; after compensation the old link is restored and the replace can be attempted again safely.
- Check CompensateReplacementAsync logs to confirm the previous link was actually restored before retrying; if this error's sibling messages appear, repair links manually.
- Make user deletion flow through the provisioner so replacement targets are not deleted mid-operation.
- 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
- Prevent concurrent deletes of users involved in re-link operations
- Retry the replace once; compensation guarantees consistent state first
- Verify link state after compensation via LinkExistsAsync before retrying
- Use locks/queueing per external subject for replace operations
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
- The Elsa user was deleted while its external identity link…
- The replacement link was removed after its target user was…
- The restored previous link was removed after its user was…
- External identity provisioning failed and its just-in-time…
- Register with configured before calling , or call with a…
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)