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 created.
What it means
Thrown by EFCoreExternalIdentityProvisioner.EnsureLinkedUserStillExistsAsync, called at the end of CreateLinkOrGetExistingAsync. After creating the external identity link, the provisioner re-checks (via IUserProvisioningService.ExistsAsync) that the just-in-time Elsa user still exists; if a concurrent actor deleted the user, the freshly created link is deleted and this error is thrown so no dangling link or credentials are issued.
Solutions
- Retry the external sign-in flow; the next attempt will recreate the just-in-time user and link from scratch.
- Ensure IUserProvisioningService.RemoveAsync callers also remove dependent identity links atomically (same transaction or cascade) to avoid the race.
- Serialize user-deletion operations against sign-in provisioning (e.g. unique constraint on link user id plus retry on conflict) if concurrent deletes are frequent.
- Check logs for who deleted the user (cleanup jobs, admin actions) and adjust job schedules to avoid first-login windows.
Example fix
// before: blind delete then throw (current compensation is automatic; caller should retry)
catch (InvalidOperationException ex) when (ex.Message.Contains("deleted while its external identity link was being created"))
{
// after: retry provisioning once
return await provisioner.CreateLinkOrGetExistingAsync(request, cancellationToken);
} Defensive patterns
Strategy: retry
Validate before calling
// Nothing the caller can pre-validate (pure race), but callers can check first: bool userExists = await provisioningService.ExistsAsync(user, wasCreated: true, ct);
Try / catch
try { link = await provisioner.CreateLinkOrGetExistingAsync(request, ct); }
catch (InvalidOperationException ex) when (ex.Message.Contains("deleted while its external identity link was being created"))
{
link = await provisioner.CreateLinkOrGetExistingAsync(request, ct); // retry: recreates user + link
} Prevention
- Coordinate user-deletion jobs with sign-in provisioning windows
- Retry external sign-in flows on this specific exception
- Route all user deletions through the provisioning service so links are cleaned atomically
- Log and alert on this error to detect racing cleanup jobs
When it happens
Trigger: CreateLinkOrGetExistingAsync completes link insertion, but between user creation and the existence re-check another process/request deletes the Elsa user (e.g. admin deletion, cleanup job, cascade from tenant removal). ExistsAsync then returns false and the compensating ExecuteDeleteAsync removes the link before throwing.
Common situations: An administrator deletes the user while the OIDC callback is in flight; a scheduled user-cleanup job races with first-time federated sign-in; duplicate concurrent sign-ins where one request's compensation deletes a shared user row.
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…
- Failed to insert AI conversation
- Failed to insert AI proposal
- The replacement link was removed after its target user was…
- External identity provisioning failed and its just-in-time…
AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13).
Data as JSON: /api/errors/a908858c53b24ed9.
Report an issue: GitHub.
Appendix: source
Thrown at src/modules/Elsa.ExternalAuthentication.Persistence.EFCore/Stores/EFCoreExternalIdentityProvisioner.cs:264
{
await using var dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
return await dbContext.ExternalIdentityLinks
.Where(x => x.Id == linkId && x.TenantId == tenantId)
.ExecuteDeleteAsync(cancellationToken) > 0;
}
private async ValueTask EnsureLinkedUserStillExistsAsync(
PersistedExternalIdentityLink link,
User user,
bool wasCreated,
CancellationToken cancellationToken)
{
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,View on GitHub (pinned to fe9217bdfa)