elsa-workflows/elsa-core · critical · AggregateException

External identity provisioning failed and its just-in-time…

Error message

External identity provisioning failed and its just-in-time user could not be removed. No credentials were issued.

What it means

Thrown by RemoveStrandedUserAsync (called from CreateLinkOrGetExistingAsync's failure path). When creating the external identity link fails after a just-in-time Elsa user was created, the provisioner tries to remove that user; if removal also fails it logs the removal error and throws an AggregateException combining the original link exception and the removal exception. No credentials are issued in this state.

Solutions

  1. Resolve the inner exceptions: fix the original link-creation failure and the reason user removal failed (constraints, connectivity).
  2. Manually delete the stranded JIT user (and any partial link rows) so the next sign-in can start clean.
  3. Retry sign-in once the user store is healthy; provisioning will recreate the user and link.
  4. Add cascading deletes or cleanup jobs for orphaned JIT users so a failed removal self-heals.

Example fix

// before
var link = await provisioner.CreateLinkOrGetExistingAsync(req, ct);
// after
catch (AggregateException agg) when (agg.Message.Contains("just-in-time user could not be removed"))
{
    logger.LogError(agg, "stranded user {UserId} needs manual cleanup", userId);
    // clean up stranded user, then retry
}
Defensive patterns

Strategy: try-catch

Validate before calling

bool stranded = !await LinkExistsAsync(linkId, ct) && await userExistsInStore(user.Id, ct); // user exists with no link

Try / catch

try { link = await provisioner.CreateLinkOrGetExistingAsync(req, ct); }
catch (AggregateException agg) when (agg.Message.Contains("just-in-time user could not be removed"))
{
    foreach (var inner in agg.InnerExceptions) logger.LogError(inner, "Link creation and user cleanup both failed");
    // delete the stranded JIT user, then retry sign-in
}

Prevention

When it happens

Trigger: Link creation throws (e.g. DB write failure, duplicate link), then _userProvisioningService.RemoveAsync(user) also throws — directory/store outage, dependent rows blocking delete, or cancellation — producing the AggregateException.

Common situations: Database outage during first federated sign-in; FK constraints or concurrent sessions preventing user deletion; the JIT user already removed by a concurrent request; storage provider misconfiguration.

Related errors


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

Appendix: source

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

        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,
                exception);
        }
    }

    private static ExternalIdentityLink ToModel(PersistedExternalIdentityLink link) => new(link.Id, link.TenantId, link.ConnectionKey, link.Issuer, link.SubjectHash, link.SubjectHint, link.UserId, link.CreatedAt, link.LastSignedInAt);

}

View on GitHub (pinned to fe9217bdfa)