elsa-workflows/elsa-core · error · NotSupportedException
This external identity provisioner does not support atomic…
Error message
This external identity provisioner does not support atomic link replacement.
What it means
The default implementation of IExternalIdentityProvisionerAdministrator.ReplaceAsync is a NotSupportedException: atomic link replacement (remove one tenant-scoped link and create its replacement in one operation, or report the conflict unchanged) is an optional capability. Provisioners that cannot perform the swap atomically throw this to signal the caller must use the non-atomic remove+create path or a different provisioner.
Solutions
- Check capability before calling (e.g. whether the provisioner type overrides ReplaceAsync) and fall back to RemoveAsync + link creation in a compensating flow
- Implement ReplaceAsync in your custom provisioner if atomic swap semantics are required
- Update the admin flow to surface 'replacement not supported' for provisioners lacking the capability instead of letting it throw
- Use a provisioner implementation that supports atomic replacement for the tenant in question
Example fix
// before
var result = await administrator.ReplaceAsync(request, ct); // throws NotSupportedException
// after
ExternalIdentityLinkReplaceResult result;
try { result = await administrator.ReplaceAsync(request, ct); }
catch (NotSupportedException)
{
await administrator.RemoveAsync(new(request.TenantId, request.LinkId), ct);
result = await provisioner.LinkAsync(request.CreateRequest, ct);
} Defensive patterns
Strategy: try-catch
Try / catch
try { return await administrator.ReplaceAsync(request, ct); }
catch (NotSupportedException)
{
// fall back: remove + create in a compensating sequence
await administrator.RemoveAsync(new(request.TenantId, request.LinkId), ct);
return await provisioner.LinkAsync(request.ToCreateRequest(), ct);
} Prevention
- Feature-detect ReplaceAsync support per provisioner before exposing relink UI
- Document which provisioners implement atomic replacement
- Prefer provisioner implementations that override ReplaceAsync for admin workflows
- Test link-replacement flows against every registered provisioner
When it happens
Trigger: Calling ReplaceAsync on an IExternalIdentityProvisioner (cast to IExternalIdentityProvisionerAdministrator or injected as such) whose concrete implementation does not override the default interface method — i.e. any provisioner that did not opt into atomic link replacement.
Common situations: Admin tooling that assumes all provisioners support relinking a user to a different external identity; migrating identity providers where the old provisioner class predates the ReplaceAsync capability; generic administration UIs invoking ReplaceAsync uniformly across provisioners.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
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…
- A unique Elsa user name could not be reserved for the…
- A configured default role no longer exists.
AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13).
Data as JSON: /api/errors/4f997cb23368e31e.
Report an issue: GitHub.
Appendix: source
Thrown at src/modules/Elsa.ExternalAuthentication/Contracts/ExternalAuthenticationContracts.cs:172
public interface IExternalIdentityProvisioner
{
/// <summary>
/// Finds the link for a normalized external identity without exposing its persisted subject representation.
/// </summary>
ValueTask<ExternalIdentityLink?> FindLinkAsync(string tenantId, string connectionKey, ExternalIdentity identity, CancellationToken cancellationToken = default);
/// <summary>
/// Creates the requested link and, when requested, its credential-less user; compensates a losing writer; or returns the winner of a concurrent operation.
/// </summary>
ValueTask<ProvisioningResult> CreateLinkOrGetExistingAsync(ProvisioningRequest request, CancellationToken cancellationToken = default);
/// <summary>
/// Atomically removes the tenant-scoped link identified by <see cref="ExternalIdentityLinkReplaceRequest.LinkId"/>
/// and creates its replacement, or returns the conflicting link without changing the original.
/// </summary>
ValueTask<ExternalIdentityLinkReplaceResult> ReplaceAsync(ExternalIdentityLinkReplaceRequest request, CancellationToken cancellationToken = default) =>
throw new NotSupportedException("This external identity provisioner does not support atomic link replacement.");
}
/// <summary>
/// Provides tenant-bounded administration of existing external identity links.
/// Creation remains on <see cref="IExternalIdentityProvisioner"/> so administrator prelinks and JIT provisioning use the same atomic tuple operation.
/// </summary>
public interface IExternalIdentityLinkManagementStore
{
ValueTask<Page<ExternalIdentityLink>> FindAsync(ExternalIdentityLinkFilter filter, CancellationToken cancellationToken = default);
ValueTask<bool> DeleteAsync(string tenantId, string linkId, CancellationToken cancellationToken = default);
}
public interface IPermissionGrantSource
{
string Type { get; }
PermissionGrantSourceDescriptor Describe();
ValueTask<PermissionGrantResult> GetGrantsAsync(PermissionGrantContext context, CancellationToken cancellationToken = default);
}View on GitHub (pinned to fe9217bdfa)