elsa-workflows/elsa-core · error · InvalidOperationException
A unique Elsa user name could not be reserved for the…
Error message
A unique Elsa user name could not be reserved for the external identity.
What it means
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.
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).
Example fix
// before: callback that always fails under contention
bool TryReserve(string name) => linkTable.TryAdd(name, externalId); // table locked/full
// after: surface the underlying cause and allow retry
bool TryReserve(string name)
{
try { return linkTable.TryAdd(name, externalId); }
catch (Exception ex) { logger.LogError(ex, "Name reservation failed"); throw; }
} Defensive patterns
Strategy: retry
Validate before calling
bool TryReserve(string name) => reservationTable.TryReserve(name, externalId); // must be a healthy, non-always-false reservation check
Try / catch
try
{
var (user, created) = await provisioning.ResolveAsync(request, ct: ct);
}
catch (InvalidOperationException ex) when (ex.Message == "A unique Elsa user name could not be reserved for the external identity.")
{
logger.LogError(ex, "Could not reserve a unique user name after 10 attempts");
throw; // investigate reservation callback / persistence health before retrying
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- The requested Elsa user does not exist.
- The requested Elsa user is outside the target tenant.
- A user creation proposal is required for an unlinked…
- Failed to insert AI conversation
- Failed to insert AI proposal
AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13).
Data as JSON: /api/errors/63e5debdffef0ac9.
Report an issue: GitHub.
Appendix: source
Thrown at src/modules/Elsa.ExternalAuthentication/Services/ExternalIdentityUserProvisioningService.cs:88
{ Id = user.Id }, CancellationToken.None);
if (persistedUser is not null)
await userStore.DeleteAsync(new()
{ Id = user.Id }, CancellationToken.None);
throw;
}
catch
{
var persistedUser = await userProvider.FindAsync(new()
{ Id = user.Id }, cancellationToken);
if (persistedUser is not null)
return (persistedUser, true);
if (await userProvider.FindAsync(new()
{ Name = name }, cancellationToken) is null)
throw;
}
}
throw new InvalidOperationException("A unique Elsa user name could not be reserved for the external identity.");
}
/// <summary>
/// Removes a user created by an operation that could not publish its external identity link.
/// </summary>
public Task RemoveAsync(User user, CancellationToken cancellationToken = default) =>
userStore.DeleteAsync(new()
{ Id = user.Id }, cancellationToken);
/// <summary>
/// Checks that the resolved user still exists in the source that supplied it.
/// </summary>
public async ValueTask<bool> ExistsAsync(User user, bool wasCreated, CancellationToken cancellationToken = default) =>
wasCreated
? await userStore.FindAsync(new()
{ Id = user.Id }, cancellationToken) is not null
: await userProvider.FindAsync(new()
{ Id = user.Id }, cancellationToken) is not null;View on GitHub (pinned to fe9217bdfa)