elsa-workflows/elsa-core · critical · AggregateException
A replacement-compensation link refers to a deleted user…
Error message
A replacement-compensation link refers to a deleted user and could not be removed. No credentials were issued.
What it means
RemoveReplacementLinksOrThrowAsync is the last-resort cleanup when a replacement-compensation link references a deleted user. If even the multi-link ExecuteDeleteAsync fails, it throws an AggregateException combining the original operation exception and the cleanup exception, stating that no credentials were issued. Callers therefore never receive credentials while a link-to-deleted-user may exist.
Solutions
- Check both inner exceptions to distinguish the original failure from the cleanup failure and fix the DB/connectivity root cause.
- Manually delete ExternalIdentityLinks rows with the recorded link ids that reference deleted users.
- Enable transient-fault retry (EnableRetryOnFailure) so cleanup deletes can succeed on retry.
- Retry external sign-in after the database is reachable; provisioning will recreate consistent state.
Example fix
// diagnosing
try { await provisioner.ReplaceAsync(...); }
catch (AggregateException agg) when (agg.Message.Contains("replacement-compensation link"))
{
foreach (var inner in agg.InnerExceptions) logger.LogError(inner, "replace/cleanup failure");
} Defensive patterns
Strategy: try-catch
Validate before calling
bool orphan = !await userProvisioningService.ExistsAsync(user, false, ct)
&& await LinkExistsAsync(linkId, ct); // link exists but user gone: needs cleanup Try / catch
try { await provisioner.ReplaceAsync(...); }
catch (AggregateException agg) when (agg.Message.Contains("replacement-compensation link"))
{
foreach (var inner in agg.InnerExceptions) logger.LogError(inner, "Operation + cleanup both failed");
// DB/link repair required before issuing any credentials
} Prevention
- Inspect both InnerExceptions: the first is the original failure, the second the cleanup failure
- Enable retrying execution strategies for transient DB faults
- Manually purge link rows referencing deleted users after incidents
- Ensure credentials are only issued after successful link persistence
When it happens
Trigger: Compensation detects a link whose user is gone; the final cleanup ExecuteDeleteAsync over (oldLinkId, replacementLinkId) itself throws (connection failure, deadlock, timeout, cancellation), triggering the AggregateException path.
Common situations: Database outage during federated sign-in compensation; lock contention with cleanup jobs; misconfigured connection string causing repeated write failures during the same request.
Related errors
- External identity provisioning failed and its just-in-time…
- AI conversation entity metadata was not found.
- AI conversation table metadata was not found.
- The replacement link was removed after its target user was…
- The restored previous link was removed after its user was…
AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13).
Data as JSON: /api/errors/c72446dd8e8ce1de.
Report an issue: GitHub.
Appendix: source
Thrown at src/modules/Elsa.ExternalAuthentication.Persistence.EFCore/Stores/EFCoreExternalIdentityProvisioner.cs:364
return await dbContext.ExternalIdentityLinks.AsNoTracking().AnyAsync(x => x.Id == linkId, cancellationToken);
}
private async ValueTask RemoveReplacementLinksOrThrowAsync(
string oldLinkId,
string replacementLinkId,
Exception operationException,
CancellationToken cancellationToken)
{
try
{
await using var cleanupContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
await cleanupContext.ExternalIdentityLinks
.Where(x => x.Id == replacementLinkId || x.Id == oldLinkId)
.ExecuteDeleteAsync(cancellationToken);
}
catch (Exception cleanupException)
{
throw new AggregateException(
"A replacement-compensation link refers to a deleted user and could not be removed. No credentials were issued.",
operationException,
cleanupException);
}
}
private async ValueTask RemoveStrandedUserAsync(User user, Exception linkException, CancellationToken cancellationToken)
{
try
{
await _userProvisioningService.RemoveAsync(user, cancellationToken);
}
catch (Exception exception)
{
logger.LogError(exception, "Could not remove the just-in-time user {UserId} after its external identity link failed", user.Id);
throw new AggregateException(
"External identity provisioning failed and its just-in-time user could not be removed. No credentials were issued.",
linkException,View on GitHub (pinned to fe9217bdfa)