{"record":{"id":"63e5debdffef0ac9","repo":"elsa-workflows/elsa-core","slug":"a-unique-elsa-user-name-could-not-be-reserved-for-the","errorCode":null,"errorMessage":"A unique Elsa user name could not be reserved for the external identity.","messagePattern":"A unique Elsa user name could not be reserved for the external identity\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/modules/Elsa.ExternalAuthentication/Services/ExternalIdentityUserProvisioningService.cs","lineNumber":88,"sourceCode":"                    { Id = user.Id }, CancellationToken.None);\n                if (persistedUser is not null)\n                    await userStore.DeleteAsync(new()\n                        { Id = user.Id }, CancellationToken.None);\n                throw;\n            }\n            catch\n            {\n                var persistedUser = await userProvider.FindAsync(new()\n                    { Id = user.Id }, cancellationToken);\n                if (persistedUser is not null)\n                    return (persistedUser, true);\n                if (await userProvider.FindAsync(new()\n                        { Name = name }, cancellationToken) is null)\n                    throw;\n            }\n        }\n\n        throw new InvalidOperationException(\"A unique Elsa user name could not be reserved for the external identity.\");\n    }\n\n    /// <summary>\n    /// Removes a user created by an operation that could not publish its external identity link.\n    /// </summary>\n    public Task RemoveAsync(User user, CancellationToken cancellationToken = default) =>\n        userStore.DeleteAsync(new()\n            { Id = user.Id }, cancellationToken);\n\n    /// <summary>\n    /// Checks that the resolved user still exists in the source that supplied it.\n    /// </summary>\n    public async ValueTask<bool> ExistsAsync(User user, bool wasCreated, CancellationToken cancellationToken = default) =>\n        wasCreated\n            ? await userStore.FindAsync(new()\n                { Id = user.Id }, cancellationToken) is not null\n            : await userProvider.FindAsync(new()\n                { Id = user.Id }, cancellationToken) is not null;","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/modules/Elsa.ExternalAuthentication/Services/ExternalIdentityUserProvisioningService.cs#L70-L106","documentation":"After MaximumUserNameAttempts (10) generated candidate names, ResolveAsync gives up and throws. A name is only usable when the optional tryReserveUserName callback accepts it AND no user with that name already exists in the user provider; on a failed save, a name collision with the same name also aborts the loop. This means the service could not generate a unique, reservable user name for the new external identity.","triggerScenarios":"Calling ResolveAsync without ExistingUserId when every generated candidate name (prefix-<id>) is either rejected by tryReserveUserName or already exists in the user store — e.g. a reservation callback that always returns false, a legacy user store full of colliding names, or repeated saves failing while a same-named user gets persisted by a concurrent process.","commonSituations":"Custom tryReserveUserName implementations (e.g. external identity-link tables) that permanently reject names due to a full/corrupt link table; extremely high name collisions after data imports; concurrent provisioning storms creating the same prefix names; database failures during SaveAsync followed by a name-only match that hides the real error.","solutions":["Inspect the tryReserveUserName callback: it must return true for available names; fix any backing reservation table/lock that is rejecting all candidates.","Retry the login/provisioning flow — collisions are expected to be transient given random generated IDs.","Check the user store for a large number of pre-existing names matching the configured prefix and change the UserNamePrefix in the proposal.","Investigate persistence errors during SaveAsync (the real failure may be masked when a same-named user appears in the recovery check)."],"exampleFix":"// before: callback that always fails under contention\nbool TryReserve(string name) => linkTable.TryAdd(name, externalId); // table locked/full\n// after: surface the underlying cause and allow retry\nbool TryReserve(string name)\n{\n    try { return linkTable.TryAdd(name, externalId); }\n    catch (Exception ex) { logger.LogError(ex, \"Name reservation failed\"); throw; }\n}","handlingStrategy":"retry","validationCode":"bool TryReserve(string name) => reservationTable.TryReserve(name, externalId); // must be a healthy, non-always-false reservation check","typeGuard":null,"tryCatchPattern":"try\n{\n    var (user, created) = await provisioning.ResolveAsync(request, ct: ct);\n}\ncatch (InvalidOperationException ex) when (ex.Message == \"A unique Elsa user name could not be reserved for the external identity.\")\n{\n    logger.LogError(ex, \"Could not reserve a unique user name after 10 attempts\");\n    throw; // investigate reservation callback / persistence health before retrying\n}","preventionTips":["Keep the tryReserveUserName callback cheap, idempotent, and backed by a healthy table with capacity.","Use a distinctive UserNamePrefix to reduce collision odds with existing data.","Monitor persistence errors during SaveAsync — repeated failures surface as this error.","Serialize concurrent provisioning for the same external identity to avoid name races."],"tags":["identity","name-collision","provisioning","concurrency"],"backgroundTag":"unique-constraint-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"}