{"record":{"id":"a908858c53b24ed9","repo":"elsa-workflows/elsa-core","slug":"the-elsa-user-was-deleted-while-its-external-identity-link","errorCode":null,"errorMessage":"The Elsa user was deleted while its external identity link was being created.","messagePattern":"The Elsa user was deleted while its external identity link was being created\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/modules/Elsa.ExternalAuthentication.Persistence.EFCore/Stores/EFCoreExternalIdentityProvisioner.cs","lineNumber":264,"sourceCode":"    {\n        await using var dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);\n        return await dbContext.ExternalIdentityLinks\n            .Where(x => x.Id == linkId && x.TenantId == tenantId)\n            .ExecuteDeleteAsync(cancellationToken) > 0;\n    }\n\n    private async ValueTask EnsureLinkedUserStillExistsAsync(\n        PersistedExternalIdentityLink link,\n        User user,\n        bool wasCreated,\n        CancellationToken cancellationToken)\n    {\n        if (await _userProvisioningService.ExistsAsync(user, wasCreated, cancellationToken))\n            return;\n\n        await using var dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);\n        await dbContext.ExternalIdentityLinks.Where(x => x.Id == link.Id).ExecuteDeleteAsync(cancellationToken);\n        throw new InvalidOperationException(\"The Elsa user was deleted while its external identity link was being created.\");\n    }\n\n    private async ValueTask EnsureReplacementUserStillExistsAsync(\n        PersistedExternalIdentityLink oldLink,\n        PersistedExternalIdentityLink replacementLink,\n        User replacementUser,\n        CancellationToken cancellationToken)\n    {\n        if (await _userProvisioningService.ExistsAsync(replacementUser, false, cancellationToken))\n            return;\n\n        await CompensateReplacementAsync(oldLink, replacementLink, cancellationToken);\n        throw new InvalidOperationException(\"The Elsa user was deleted while its external identity link was being replaced.\");\n    }\n\n    private async ValueTask CompensateReplacementAsync(\n        PersistedExternalIdentityLink oldLink,\n        PersistedExternalIdentityLink replacementLink,","sourceCodeStart":246,"sourceCodeEnd":282,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/modules/Elsa.ExternalAuthentication.Persistence.EFCore/Stores/EFCoreExternalIdentityProvisioner.cs#L246-L282","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: blind delete then throw (current compensation is automatic; caller should retry)\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"deleted while its external identity link was being created\"))\n{\n    // after: retry provisioning once\n    return await provisioner.CreateLinkOrGetExistingAsync(request, cancellationToken);\n}","handlingStrategy":"retry","validationCode":"// Nothing the caller can pre-validate (pure race), but callers can check first:\nbool userExists = await provisioningService.ExistsAsync(user, wasCreated: true, ct);","typeGuard":null,"tryCatchPattern":"try { link = await provisioner.CreateLinkOrGetExistingAsync(request, ct); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"deleted while its external identity link was being created\"))\n{\n    link = await provisioner.CreateLinkOrGetExistingAsync(request, ct); // retry: recreates user + link\n}","preventionTips":["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"],"tags":["race-condition","ef-core","user-provisioning","concurrency"],"backgroundTag":"internal-invariant-violation","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"}