elsa-workflows/elsa-core · error · InvalidOperationException

The secret binding resolver is unavailable.

Error message

The secret binding resolver is unavailable.

What it means

ConnectionTestService resolves each SecretBinding via a dictionary of registered ISecretBindingResolver implementations keyed by resolver Type. When a binding's ResolverType has no matching registered resolver, the test cannot obtain the secret and throws this error; the surrounding catch in TestAsync converts it into a failed connection observation.

Solutions

  1. Register the ISecretBindingResolver implementation matching binding.ResolverType in the DI container.
  2. Add the Elsa ExternalAuthentication feature/module that provides the missing resolver at startup.
  3. Check the stored connection's SecretBindings for a typo'd or obsolete ResolverType and correct it.
  4. Verify the resolver type name is identical across environments where the connection is used.

Example fix

// before
services.AddExternalAuthentication(); // no resolvers registered
// after
services.AddExternalAuthentication()
        .AddConfigurationSecretBindingResolver(); // registers resolver for "Configuration" type
Defensive patterns

Strategy: validation

Validate before calling

// At startup or before testing, check every binding has a registered resolver
var resolverTypes = secretBindingResolvers.Select(r => r.Type).ToHashSet(StringComparer.Ordinal);
foreach (var (name, binding) in connection.SecretBindings)
    if (!resolverTypes.Contains(binding.ResolverType))
        throw new InvalidOperationException($"No secret binding resolver registered for type '{binding.ResolverType}' (binding '{name}').");

Try / catch

try
{
    await testService.TestAsync(connectionId, revision, tenantId, actor, ct);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("resolver is unavailable"))
{
    logger.LogWarning("Connection test skipped: resolver missing for binding type.");
}

Prevention

When it happens

Trigger: Thrown from ResolveSecretsAsync (called by TestAsync) when connection.Connection.SecretBindings contains an entry whose binding.ResolverType is not present in the registered resolver dictionary — i.e. the resolver for that type string was never registered in DI.

Common situations: The feature/package providing the resolver (e.g. configuration or a vault resolver) is not registered in the host; a typo or renamed resolver type in stored connection data; a custom resolver type referenced by connections but deployed to only some environments.

Understand the failure class

Background: "not installed", "pip install", "required for": how missing-dependency errors surface across open-source libraries — this error's family across 34 libraries.

Related errors


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

Appendix: source

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

                null,
                observation.Status == ConnectionObservationStatus.Failed ? SecurityEventOutcome.Failed : SecurityEventOutcome.Succeeded,
                "Identity provider connection test completed."),
            observation.TestedMaterialRevision,
            observation.Status.ToString().ToLowerInvariant(),
            observation.Category,
            observation.Duration), cancellationToken);
        return new ConnectionTestOperationResult.Completed(observation);
    }

    private async ValueTask<IReadOnlyDictionary<string, ResolvedSecretBinding>> ResolveSecretsAsync(IDictionary<string, SecretBinding> bindings, CancellationToken cancellationToken)
    {
        var result = new Dictionary<string, ResolvedSecretBinding>(StringComparer.Ordinal);
        try
        {
            foreach (var (name, binding) in bindings)
            {
                if (!_resolvers.TryGetValue(binding.ResolverType, out var resolver))
                    throw new InvalidOperationException("The secret binding resolver is unavailable.");
                result[name] = await resolver.ResolveAsync(binding, cancellationToken);
            }
            return result;
        }
        catch
        {
            foreach (var secret in result.Values)
                secret.Value.Dispose();
            throw;
        }
    }

    private static string? ActorId(ClaimsPrincipal actor) => actor.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? actor.FindFirst("sub")?.Value;
    // Adapter messages are already contractually safe, but cap them at a predictable diagnostic size.
    private static string SafeSummary(string value) => string.IsNullOrWhiteSpace(value) ? "No additional details are available." : value.Length <= 512 ? value : value[..512];
    private static string SafeCategory(string value) => string.IsNullOrWhiteSpace(value) ? "unknown" : value.Length <= 128 ? value : "unknown";
}

View on GitHub (pinned to fe9217bdfa)