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 FileSecretRepository.AddAsync when a secret with the same name (case-insensitive) already exists in the file-backed store. AddAsync only creates new secrets; it never updates, so duplicate names are rejected as InvalidOperationException.

Solutions

  1. Check existence first with repository/manager GetAsync(name) and update (SaveAsync) instead of AddAsync when it exists.
  2. Use AddOrGetExistingAsync, which tolerates existing secrets and returns the existing one.
  3. Catch InvalidOperationException and treat the existing secret as the result (upsert semantics in caller code).
  4. Add idempotency to seeding scripts (skip names already present).

Example fix

// before
await repository.AddAsync(new Secret { Name = "api-key", ... });
// after
var existing = await manager.GetAsync("api-key");
if (existing == null)
    await manager.CreateAsync(new CreateSecretRequest { Name = "api-key", ... });
else
    await manager.UpdateAsync("api-key", new UpdateSecretRequest { ... });
Defensive patterns

Strategy: validation

Validate before calling

var existing = await manager.GetAsync(name);
if (existing != null) /* update/rotate instead of add */;

Try / catch

try { await repository.AddAsync(secret); }
catch (InvalidOperationException e) when (e.Message.Contains("already exists")) { /* fetch existing and update instead */ }

Prevention

When it happens

Trigger: Calling ISecretRepository.AddAsync (directly or via AddOrGetExistingAsync) with a Secret whose Name matches an existing secret in the file store, ignoring case.

Common situations: Re-running an initialization/seed script that adds secrets without checking existence, concurrent provisioning racing to create the same secret, or retry logic re-submitting a creation that already 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/1db74b47c5269bb3. Report an issue: GitHub.

Appendix: source

Thrown at src/modules/Elsa.Secrets/Repositories/FileSecretRepository.cs:35

    public async Task<Secret?> GetAsync(string normalizedName, CancellationToken cancellationToken = default)
    {
        var secrets = await ReadAllAsync(cancellationToken);
        return secrets.FirstOrDefault(x => string.Equals(x.Name, normalizedName, StringComparison.OrdinalIgnoreCase));
    }

    public async Task<IReadOnlyCollection<Secret>> ListAsync(CancellationToken cancellationToken = default)
    {
        return await ReadAllAsync(cancellationToken);
    }

    public async Task AddAsync(Secret secret, CancellationToken cancellationToken = default)
    {
        await _lock.WaitAsync(cancellationToken);
        try
        {
            var secrets = await ReadAllUnsafeAsync(cancellationToken);
            if (secrets.Any(x => string.Equals(x.Name, secret.Name, StringComparison.OrdinalIgnoreCase)))
                throw new InvalidOperationException($"A secret named '{secret.Name}' already exists.");

            secrets.Add(secret);
            await WriteAllUnsafeAsync(secrets, cancellationToken);
        }
        finally
        {
            _lock.Release();
        }
    }

    public async Task<bool> TryAddOrReplaceDeletedAsync(Secret secret, CancellationToken cancellationToken = default)
    {
        await _lock.WaitAsync(cancellationToken);
        try
        {
            var secrets = await ReadAllUnsafeAsync(cancellationToken);
            var index = secrets.FindIndex(x => string.Equals(x.Name, secret.Name, StringComparison.OrdinalIgnoreCase));
            if (index >= 0)

View on GitHub (pinned to fe9217bdfa)