elsa-workflows/elsa-core · error · InvalidOperationException

A secret named ' ' already exists.

Error message

A secret named '{secret.Name}' already exists.

What it means

Thrown by InMemorySecretRepository.AddAsync when a secret with the same name (case-insensitive) is already present in the in-memory dictionary. AddAsync uses ConcurrentDictionary.TryAdd, so any existing entry - including a previously deleted-but-present secret in some flows - causes this InvalidOperationException.

Solutions

  1. Reset or recreate the in-memory repository (or remove the existing entry) before adding in tests/startup code.
  2. Check for existence first and skip or update instead of adding.
  3. Use AddOrGetExistingAsync so an existing secret is returned rather than throwing.
  4. Make creation idempotent by catching InvalidOperationException and verifying the existing secret matches the desired state.

Example fix

// before
await inMemoryRepository.AddAsync(secret); // throws on rerun
// after
var existing = await manager.GetAsync(secret.Name);
if (existing == null)
    await inMemoryRepository.AddAsync(secret);
Defensive patterns

Strategy: validation

Validate before calling

if (await manager.GetAsync(secret.Name) != null) return; // already added

Try / catch

try { await inMemoryRepository.AddAsync(secret); }
catch (InvalidOperationException e) when (e.Message.Contains("already exists")) { /* reuse existing entry */ }

Prevention

When it happens

Trigger: Calling InMemorySecretRepository.AddAsync with a Secret whose Name collides with an existing key in the dictionary; also reached via AddOrGetExistingAsync when the name already exists.

Common situations: Unit tests reusing a shared in-memory repository across test cases without resetting it, seeding the same secret name twice at startup, or retrying a creation call after a transient failure when the first attempt actually succeeded.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


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

Appendix: source

Thrown at src/modules/Elsa.Secrets/Repositories/InMemorySecretRepository.cs:23

public class InMemorySecretRepository : ISecretRepository
{
    private readonly ConcurrentDictionary<string, Secret> _secrets = new(StringComparer.OrdinalIgnoreCase);

    public Task<Secret?> GetAsync(string normalizedName, CancellationToken cancellationToken = default)
    {
        _secrets.TryGetValue(normalizedName, out var secret);
        return Task.FromResult(secret == null ? null : Clone(secret));
    }

    public Task<IReadOnlyCollection<Secret>> ListAsync(CancellationToken cancellationToken = default)
    {
        return Task.FromResult<IReadOnlyCollection<Secret>>(_secrets.Values.Select(Clone).ToList());
    }

    public Task AddAsync(Secret secret, CancellationToken cancellationToken = default)
    {
        if (!_secrets.TryAdd(secret.Name, Clone(secret)))
            throw new InvalidOperationException($"A secret named '{secret.Name}' already exists.");

        return Task.CompletedTask;
    }

    public Task<bool> TryAddOrReplaceDeletedAsync(Secret secret, CancellationToken cancellationToken = default)
    {
        var secretClone = Clone(secret);

        while (true)
        {
            if (!_secrets.TryGetValue(secret.Name, out var existingSecret))
                return Task.FromResult(_secrets.TryAdd(secret.Name, secretClone));

            if (existingSecret.Status != SecretStatus.Deleted)
                return Task.FromResult(false);

            if (_secrets.TryUpdate(secret.Name, secretClone, existingSecret))
                return Task.FromResult(true);

View on GitHub (pinned to fe9217bdfa)