elsa-workflows/elsa-core · error · InvalidOperationException
Only managed secret bindings can remove managed secret…
Error message
Only managed secret bindings can remove managed secret material.
What it means
RemoveAsync refuses to delete secret material referenced by a binding whose Ownership is not SecretBindingOwnership.Managed. This protects externally owned (e.g. configuration-backed read-only) bindings from having their referenced secret material deleted through this resolver.
Solutions
- Check binding.Ownership == SecretBindingOwnership.Managed before calling RemoveAsync
- Remove the binding from the source that owns it instead of deleting managed material
- Skip non-managed bindings in bulk cleanup loops
Example fix
// before
await resolver.RemoveAsync(binding);
// after
if (binding.Ownership == SecretBindingOwnership.Managed)
await resolver.RemoveAsync(binding); Defensive patterns
Strategy: type-guard
Validate before calling
if (binding.Ownership != SecretBindingOwnership.Managed) return; // skip: not managed
Type guard
bool CanRemove(SecretBinding b) => b.Ownership == SecretBindingOwnership.Managed;
Try / catch
try { await resolver.RemoveAsync(binding); }
catch (InvalidOperationException) { /* binding not managed; skip or handle elsewhere */ } Prevention
- Filter bulk cleanup loops by Ownership == Managed before deleting
- Model ownership checks in the domain layer so callers cannot bypass them
- Never assume every binding handed out by a resolver is managed
When it happens
Trigger: Calling RemoveAsync and passing a SecretBinding whose Ownership is System/External instead of Managed; typically after fetching or constructing a binding without checking its Ownership.
Common situations: Cleanup scripts that iterate all bindings and attempt removal without filtering by ownership; code paths that assume every binding the resolver hands out is managed.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- The configured secret binding is not active.
- Cannot overwrite an AI conversation that belongs to another…
- Cannot overwrite an AI proposal that belongs to another…
- The secret field name must contain a letter or digit.
- The configured secret binding could not be resolved.
AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13).
Data as JSON: /api/errors/424e79443eadb08b.
Report an issue: GitHub.
Appendix: source
Thrown at src/modules/Elsa.ExternalAuthentication.Secrets/Services/ElsaSecretBindingResolver.cs:48
// so a stale request can never rotate material used by the live binding.
var name = $"external-authentication:{Guid.NewGuid():N}";
var secret = await secretManager.CreateAsync(new()
{
Name = name,
DisplayName = $"External authentication {request.FieldName}",
TypeName = SecretTypeNames.Text,
StoreName = SecretStoreNames.Encrypted,
Value = request.Value.Reveal()
}, cancellationToken);
return new(ResolverType, secret.Name, Ownership: SecretBindingOwnership.Managed);
}
public async ValueTask RemoveAsync(SecretBinding binding, CancellationToken cancellationToken = default)
{
EnsureResolverType(binding);
if (binding.Ownership != SecretBindingOwnership.Managed)
throw new InvalidOperationException("Only managed secret bindings can remove managed secret material.");
await secretManager.DeleteAsync(binding.Reference, cancellationToken);
}
public async ValueTask<SecretBindingState> GetStateAsync(SecretBinding binding, CancellationToken cancellationToken = default)
{
EnsureResolverType(binding);
var secret = await secretManager.GetAsync(binding.Reference, cancellationToken);
if (secret is null)
return new(false, false);
var configured = secret is { Status: SecretStatus.Active, LatestActiveVersion: not null };
if (!configured || !IsCompatible(secret, binding))
return new(configured, false);
var test = await secretManager.TestAsync(secret.Name, cancellationToken);
return new(true, test.Succeeded);
}
View on GitHub (pinned to fe9217bdfa)