elsa-workflows/elsa-core · error · InvalidOperationException

Secret store ' ' does not support writing secrets.

Error message

Secret store '{store.Name}' does not support writing secrets.

What it means

EnsureCanWrite validates that the target ISecretStore advertises the Write capability via SecretStoreCapabilities before mutating it. Read-only stores (e.g. config/deployment-backed stores) reject writes with this InvalidOperationException. It is a capability check, not an authentication failure.

Solutions

  1. Write the secret to a store that supports Write (register an encrypted/writable store).
  2. Move the secret value into the read-only store's own configuration mechanism (appsettings/env/deployment).
  3. Update the custom store's Descriptor.Capabilities to include Write if it actually supports it.
  4. Check store selection/naming so the operation targets the intended writable store.

Example fix

// before
await secretManager.CreateSecretAsync(new CreateSecretRequest { Name = "ConnectionString" }); // resolves to read-only config store
// after
await secretManager.CreateSecretAsync(new CreateSecretRequest { Name = "ConnectionString", StoreName = "ElsaEncryptedStore" }); // writable store
Defensive patterns

Strategy: validation

Validate before calling

var store = storeRegistry.GetStore(storeName);
if (store is null || !store.Descriptor.Capabilities.HasFlag(SecretStoreCapabilities.Write))
    throw new InvalidOperationException($"Store '{storeName}' is read-only; choose a writable store.");

Type guard

bool CanWrite(ISecretStore store) => store.Descriptor.Capabilities.HasFlag(SecretStoreCapabilities.Write);

Try / catch

try { await manager.CreateSecretAsync(request, ct); }
catch (InvalidOperationException ex) when (ex.Message.Contains("does not support writing")) { logger.LogError("Target store is read-only; provision '{Name}' via deployment config instead", request.Name); throw; }

Prevention

When it happens

Trigger: CreateSecretAsync or RotateAsync targeting a store whose Descriptor.Capabilities lacks SecretStoreCapabilities.Write — e.g. writing to a configuration-backed read-only store or specifying a store selector that resolves to one.

Common situations: Secret name routed to a read-only environment store; app configured with only a config-backed store; capability flags misconfigured in a custom store's Descriptor; trying to rotate infra-managed secrets from application code.

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/1c30aeececa12292. Report an issue: GitHub.

Appendix: source

Thrown at src/modules/Elsa.Secrets/Services/DefaultSecretManager.cs:219

    private static SecretVersion GetLatestActiveVersion(Secret secret)
    {
        if (secret.Status != SecretStatus.Active)
            throw new InvalidOperationException($"Secret '{secret.Name}' is not active.");

        return secret.LatestActiveVersion ?? throw new InvalidOperationException($"Secret '{secret.Name}' has no active version.");
    }

    private void ValidateName(string name)
    {
        if (!nameValidator.IsValid(name, out var error))
            throw new InvalidOperationException(error);
    }

    private static void EnsureCanWrite(ISecretStore store)
    {
        if (!store.Descriptor.Capabilities.HasFlag(SecretStoreCapabilities.Write))
            throw new InvalidOperationException($"Secret store '{store.Name}' does not support writing secrets.");
    }

    private static IEnumerable<Secret> ApplyFilters(IEnumerable<Secret> secrets, ListSecretsRequest request)
    {
        var query = secrets.Where(x => x.Status != SecretStatus.Deleted);

        if (!string.IsNullOrWhiteSpace(request.Search))
        {
            var search = request.Search.Trim();
            query = query.Where(x =>
                x.Name.Contains(search, StringComparison.OrdinalIgnoreCase) ||
                x.DisplayName.Contains(search, StringComparison.OrdinalIgnoreCase) ||
                (x.Description?.Contains(search, StringComparison.OrdinalIgnoreCase) ?? false));
        }

        var typeNames = GetFilterValues(request.TypeName, request.TypeNames);
        if (typeNames.Count > 0)
            query = query.Where(x => typeNames.Contains(x.TypeName));

View on GitHub (pinned to fe9217bdfa)