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

  1. Check capability before calling (e.g. whether the provisioner type overrides ReplaceAsync) and fall back to RemoveAsync + link creation in a compensating flow
  2. Implement ReplaceAsync in your custom provisioner if atomic swap semantics are required
  3. Update the admin flow to surface 'replacement not supported' for provisioners lacking the capability instead of letting it throw
  4. 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

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


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)