elsa-workflows/elsa-core · error · InvalidOperationException

A configuration key is required.

Error message

A configuration key is required.

What it means

ConfigurationSecretStore reads secrets from application configuration, so each write payload must carry a metadata entry naming the configuration key the secret maps to. When payload.Metadata lacks that key or it is blank, WriteAsync refuses the operation with an InvalidOperationException. No value is actually written to configuration by this store.

Solutions

  1. Add the required configuration-key metadata entry to the payload before calling WriteAsync.
  2. Validate payloads in your UI/API layer before dispatching writes to a configuration-backed store.
  3. If configuration-backed writes are not desired, use EncryptedSecretStore instead, which stores values rather than configuration references.

Example fix

// before
var payload = new SecretPayload { Value = value };
await store.WriteAsync(secret, version, payload, ct);
// after
var payload = new SecretPayload { Value = value };
payload.Metadata[ConfigurationSecretStore.ConfigurationKeyMetadataName] = "MyApp:ApiKey";
await store.WriteAsync(secret, version, payload, ct);
Defensive patterns

Strategy: validation

Validate before calling

if (!payload.Metadata.TryGetValue(ConfigurationSecretStore.ConfigurationKeyMetadataName, out var key) || string.IsNullOrWhiteSpace(key))
    throw new ArgumentException("Payload must specify the configuration key metadata before writing to ConfigurationSecretStore.");

Type guard

bool HasConfigKey(SecretPayload p) =>
    p.Metadata.TryGetValue(ConfigurationSecretStore.ConfigurationKeyMetadataName, out var k) && !string.IsNullOrWhiteSpace(k);

Try / catch

try
{
    await store.WriteAsync(secret, version, payload, ct);
}
catch (InvalidOperationException ex) when (ex.Message == "A configuration key is required.")
{
    logger.LogError("Refusing write: missing configuration key metadata for secret {SecretId}.", secret.Id);
}

Prevention

When it happens

Trigger: Calling WriteAsync on ConfigurationSecretStore with a SecretPayload whose Metadata dictionary does not contain the ConfigurationKeyMetadataName key, or contains it with null/whitespace value.

Common situations: Building payloads generically from user input without enforcing the metadata key; UI clients that omit advanced metadata; migrating secrets from another store where the metadata key was named differently.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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

Appendix: source

Thrown at src/modules/Elsa.Secrets/Stores/ConfigurationSecretStore.cs:22

namespace Elsa.Secrets.Stores;

public class ConfigurationSecretStore(IConfiguration configuration, IOptions<SecretsOptions> options) : ISecretStore
{
    private const string ConfigurationKeyMetadataName = "configurationKey";

    public string Name => SecretStoreNames.Configuration;

    public SecretStoreDescriptor Descriptor { get; } = new(
        SecretStoreNames.Configuration,
        "Configuration",
        "Reads values from application configuration without storing the value in Elsa.",
        SecretStoreCapabilities.Read | SecretStoreCapabilities.Write | SecretStoreCapabilities.Test,
        true);

    public Task<SecretPayload> WriteAsync(Secret secret, SecretVersion version, SecretPayload payload, CancellationToken cancellationToken = default)
    {
        if (!payload.Metadata.TryGetValue(ConfigurationKeyMetadataName, out var key) || string.IsNullOrWhiteSpace(key))
            throw new InvalidOperationException("A configuration key is required.");

        return Task.FromResult(new SecretPayload { Metadata = new Dictionary<string, string>(payload.Metadata, StringComparer.OrdinalIgnoreCase) });
    }

    public Task<SecretPayload?> ReadAsync(Secret secret, SecretVersion version, CancellationToken cancellationToken = default)
    {
        if (!version.Payload.Metadata.TryGetValue(ConfigurationKeyMetadataName, out var key) || string.IsNullOrWhiteSpace(key))
            return Task.FromResult<SecretPayload?>(null);

        var configuredValue = configuration[$"{options.Value.ConfigurationSectionName}:{key}"] ?? configuration[key];
        return configuredValue == null ? Task.FromResult<SecretPayload?>(null) : Task.FromResult<SecretPayload?>(SecretPayload.FromValue(configuredValue));
    }

    public Task DeleteAsync(Secret secret, CancellationToken cancellationToken = default) => Task.CompletedTask;

    public async Task<bool> TestAsync(Secret secret, SecretVersion version, CancellationToken cancellationToken = default)
    {
        var payload = await ReadAsync(secret, version, cancellationToken);

View on GitHub (pinned to fe9217bdfa)