elsa-workflows/elsa-core · error · InvalidOperationException

The secret binding selects a different resolver type.

Error message

The secret binding selects a different resolver type.

What it means

EnsureResolverType verifies that the SecretBinding's ResolverType exactly matches the resolver being asked to handle it (ordinal comparison). When the binding was registered for a different resolver implementation, this resolver throws rather than silently resolving a secret it does not own. This is a routing/misconfiguration guard inside ElsaSecretBindingResolver.

Solutions

  1. Re-stage the secret with the currently registered managed secret writer so the binding's ResolverType matches the active resolver
  2. Check binding.ResolverType against the resolver registered in DI and align configuration (connection SecretBindings) with it
  3. If multiple resolver types legitimately coexist, ensure lookups go through the resolver registry that dispatches by ResolverType rather than calling this resolver directly
  4. Fix typos in hand-written binding definitions — the comparison is ordinal, so exact casing matters

Example fix

// before: stale binding from a different resolver
connection.SecretBindings[field] = new SecretBinding { ResolverType = "LegacyWriter", Reference = "ref-1" };
// after: re-stage with the current writer
var staged = await writer.StageAsync(new(connection.Id, field, value), ct);
connection.SecretBindings[field] = staged; // ResolverType matches current resolver
Defensive patterns

Strategy: validation

Validate before calling

// before resolving
if (!string.Equals(binding.ResolverType, expectedResolverType, StringComparison.Ordinal))
    throw new InvalidOperationException($"Binding resolver type '{binding.ResolverType}' does not match '{expectedResolverType}'; re-stage the secret.");

Type guard

bool IsOwnedBy(SecretBinding b, string resolverType) => string.Equals(b.ResolverType, resolverType, StringComparison.Ordinal) && !string.IsNullOrWhiteSpace(b.Reference);

Try / catch

try { var value = await resolver.ResolveAsync(binding, ct); }
catch (InvalidOperationException ex) when (ex.Message.Contains("different resolver type"))
{ logger.LogError("Binding {Reference} belongs to another resolver ({Type}).", binding.Reference, binding.ResolverType); }

Prevention

When it happens

Trigger: Calling GetStateAsync, ResolveAsync, or RemoveAsync on the ElsaSecretBindingResolver with a SecretBinding whose binding.ResolverType differs (even by case or whitespace variant) from this resolver's ResolverType constant — typically because the connection's SecretBindings dictionary holds a binding produced by another managed secret writer.

Common situations: Switching secret writer/resolver implementations after connections were saved, so old bindings still name the previous ResolverType; hand-crafted SecretBinding entries in configuration with a typo'd ResolverType; copying bindings between environments where different resolver types are registered.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13). Data as JSON: /api/errors/3f1a542098b6ad41. Report an issue: GitHub.

Appendix: source

Thrown at src/modules/Elsa.ExternalAuthentication.Secrets/Services/ElsaSecretBindingResolver.cs:88

        var secret = await secretManager.GetAsync(binding.Reference, cancellationToken)
            ?? throw new InvalidOperationException("The configured secret binding could not be resolved.");
        if (!IsCompatible(secret, binding))
            throw new InvalidOperationException("The configured secret binding is incompatible with the required type or scope.");
        if (secret is not { Status: SecretStatus.Active, LatestActiveVersion: { } version })
            throw new InvalidOperationException("The configured secret binding is not active.");

        var payload = await secretManager.ResolvePayloadAsync(secret, cancellationToken);
        if (payload.Value is null)
            throw new InvalidOperationException("The configured secret binding could not be resolved.");

        var fingerprint = handleHasher.Hash($"{ResolverType}:{secret.Id}:{version.Version}:{version.CreatedAt.ToUnixTimeMilliseconds()}");
        return new(new(payload.Value), fingerprint);
    }

    private static void EnsureResolverType(SecretBinding binding)
    {
        if (!string.Equals(binding.ResolverType, ResolverType, StringComparison.Ordinal))
            throw new InvalidOperationException("The secret binding selects a different resolver type.");
        if (string.IsNullOrWhiteSpace(binding.Reference))
            throw new InvalidOperationException("The secret binding reference is required.");
    }

    private static bool IsCompatible(Secret secret, SecretBinding binding) =>
        (string.IsNullOrWhiteSpace(binding.ExpectedType) || string.Equals(secret.TypeName, binding.ExpectedType, StringComparison.OrdinalIgnoreCase)) &&
        (string.IsNullOrWhiteSpace(binding.ExpectedScope) || string.Equals(secret.Scope, binding.ExpectedScope, StringComparison.OrdinalIgnoreCase));
}

View on GitHub (pinned to fe9217bdfa)