{"record":{"id":"5878afff6049d3f9","repo":"elsa-workflows/elsa-core","slug":"external-identity-provisioning-failed-and-its-just-in-time","errorCode":null,"errorMessage":"External identity provisioning failed and its just-in-time user could not be removed. No credentials were issued.","messagePattern":"External identity provisioning failed and its just-in-time user could not be removed\\. No credentials were issued\\.","errorType":"exception","errorClass":"AggregateException","httpStatus":null,"severity":"critical","filePath":"src/modules/Elsa.ExternalAuthentication.Persistence.EFCore/Stores/EFCoreExternalIdentityProvisioner.cs","lineNumber":380,"sourceCode":"        catch (Exception cleanupException)\n        {\n            throw new AggregateException(\n                \"A replacement-compensation link refers to a deleted user and could not be removed. No credentials were issued.\",\n                operationException,\n                cleanupException);\n        }\n    }\n\n    private async ValueTask RemoveStrandedUserAsync(User user, Exception linkException, CancellationToken cancellationToken)\n    {\n        try\n        {\n            await _userProvisioningService.RemoveAsync(user, cancellationToken);\n        }\n        catch (Exception exception)\n        {\n            logger.LogError(exception, \"Could not remove the just-in-time user {UserId} after its external identity link failed\", user.Id);\n            throw new AggregateException(\n                \"External identity provisioning failed and its just-in-time user could not be removed. No credentials were issued.\",\n                linkException,\n                exception);\n        }\n    }\n\n    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);\n\n}\n","sourceCodeStart":362,"sourceCodeEnd":390,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/modules/Elsa.ExternalAuthentication.Persistence.EFCore/Stores/EFCoreExternalIdentityProvisioner.cs#L362-L390","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Resolve the inner exceptions: fix the original link-creation failure and the reason user removal failed (constraints, connectivity).","Manually delete the stranded JIT user (and any partial link rows) so the next sign-in can start clean.","Retry sign-in once the user store is healthy; provisioning will recreate the user and link.","Add cascading deletes or cleanup jobs for orphaned JIT users so a failed removal self-heals."],"exampleFix":"// before\nvar link = await provisioner.CreateLinkOrGetExistingAsync(req, ct);\n// after\ncatch (AggregateException agg) when (agg.Message.Contains(\"just-in-time user could not be removed\"))\n{\n    logger.LogError(agg, \"stranded user {UserId} needs manual cleanup\", userId);\n    // clean up stranded user, then retry\n}","handlingStrategy":"try-catch","validationCode":"bool stranded = !await LinkExistsAsync(linkId, ct) && await userExistsInStore(user.Id, ct); // user exists with no link","typeGuard":null,"tryCatchPattern":"try { link = await provisioner.CreateLinkOrGetExistingAsync(req, ct); }\ncatch (AggregateException agg) when (agg.Message.Contains(\"just-in-time user could not be removed\"))\n{\n    foreach (var inner in agg.InnerExceptions) logger.LogError(inner, \"Link creation and user cleanup both failed\");\n    // delete the stranded JIT user, then retry sign-in\n}","preventionTips":["Check both inner exceptions to fix link creation AND user removal causes","Run periodic cleanup for JIT users without identity links","Ensure the user store permits deletes (no blocking FK/constraints)","Add idempotent provisioning so a retry after cleanup succeeds cleanly"],"tags":["aggregate-exception","user-provisioning","cleanup","ef-core"],"backgroundTag":"database-write-failed","analyzedSha":"fe9217bdfa0e27f0e09e45006eb6898f616e513d","analyzedAt":"2026-09-13T20:32:34.702Z","contentChangedAt":"2026-09-13T20:32:34.702Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}