{"record":{"id":"12f5d9fb70f213df","repo":"elsa-workflows/elsa-core","slug":"a-secret-named-secret-name-already-exists","errorCode":null,"errorMessage":"A secret named '{secret.Name}' already exists.","messagePattern":"A secret named '(.+?)' already exists\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/modules/Elsa.Secrets.Persistence.EFCore/Repositories/EFCoreSecretRepository.cs","lineNumber":34,"sourceCode":"    }\n\n    public async Task<IReadOnlyCollection<Secret>> ListAsync(CancellationToken cancellationToken = default)\n    {\n        await using var dbContext = await store.CreateDbContextAsync(cancellationToken);\n        var secrets = await dbContext.Secrets.ToListAsync(cancellationToken);\n\n        foreach (var secret in secrets)\n            SecretSerialization.LoadSerializedProperties(dbContext, secret);\n\n        return secrets;\n    }\n\n    public async Task AddAsync(Secret secret, CancellationToken cancellationToken = default)\n    {\n        await using var dbContext = await store.CreateDbContextAsync(cancellationToken);\n        var normalizedName = secretNameValidator.Normalize(secret.Name);\n        if (await ExistsByNormalizedNameAsync(dbContext, normalizedName, cancellationToken))\n            throw new InvalidOperationException($\"A secret named '{secret.Name}' already exists.\");\n\n        await dbContext.Secrets.AddAsync(secret, cancellationToken);\n        SetNormalizedName(dbContext, secret);\n        SecretSerialization.StoreSerializedProperties(dbContext, secret);\n        await SaveChangesAsync(dbContext, secret.Name, cancellationToken);\n    }\n\n    public async Task<bool> TryAddOrReplaceDeletedAsync(Secret secret, CancellationToken cancellationToken = default)\n    {\n        await using var dbContext = await store.CreateDbContextAsync(cancellationToken);\n        var existingSecret = await FindByNameAsync(dbContext, secret.Name, cancellationToken);\n\n        if (existingSecret == null)\n        {\n            await dbContext.Secrets.AddAsync(secret, cancellationToken);\n            SetNormalizedName(dbContext, secret);\n            SecretSerialization.StoreSerializedProperties(dbContext, secret);\n            return await TrySaveChangesAsync(dbContext, secret.Name, cancellationToken);","sourceCodeStart":16,"sourceCodeEnd":52,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/modules/Elsa.Secrets.Persistence.EFCore/Repositories/EFCoreSecretRepository.cs#L16-L52","documentation":"EFCoreSecretRepository.AddAsync normalizes the incoming secret's name and checks whether a secret with that normalized name already exists. If so, it throws this InvalidOperationException before touching the database, giving a friendly duplicate-name error (case-insensitive, per the validator's normalization).","triggerScenarios":"Calling AddAsync with a secret whose Name matches (case-insensitively, after normalization) an existing secret row — e.g. adding \"ApiKey\" when \"apikey\" already exists.","commonSituations":"Retry logic re-adding a secret after a transient failure; seeding scripts that run twice; users typing a name that differs only by case from an existing secret; concurrent creation where both passed the check but only one insert wins (the other surfaces via SaveChangesAsync's variant of this error).","solutions":["Check existence first (ListAsync/GetAsync by name) and use SaveAsync/update instead of AddAsync when the secret already exists.","Remove the existing secret if the new one should replace it, then AddAsync again.","Pick a unique name for the new secret.","Race conditions: catch InvalidOperationException from AddAsync/SaveChangesAsync and fall back to update semantics."],"exampleFix":"// before\nawait repository.AddAsync(new Secret { Name = existingName });\n// after\nvar existing = await repository.GetAsync(existingName, cancellationToken);\nif (existing is null)\n    await repository.AddAsync(new Secret { Name = existingName });\nelse\n    existing.Value = newValue;\n    await repository.SaveAsync(existing, cancellationToken);","handlingStrategy":"try-catch","validationCode":"var existing = await repository.GetAsync(normalizedName, cancellationToken);\nif (existing is not null)\n    throw new InvalidOperationException($\"Secret '{normalizedName}' already exists; use SaveAsync to update.\");","typeGuard":null,"tryCatchPattern":"try\n{\n    await repository.AddAsync(secret, cancellationToken);\n}\ncatch (InvalidOperationException ex) when (ex.Message.StartsWith(\"A secret named '\"))\n{\n    logger.LogWarning(ex, \"Secret '{Name}' already exists.\", secret.Name);\n    // switch to update path or surface a duplicate-name message to the user\n}","preventionTips":["Check existence by normalized name before AddAsync.","Offer users 'create or update' semantics instead of blind AddAsync.","Make seeding scripts idempotent."],"tags":["duplicate","secrets","persistence","conflict"],"backgroundTag":"file-already-exists","analyzedSha":"fe9217bdfa0e27f0e09e45006eb6898f616e513d","analyzedAt":"2026-09-13T20:32:34.702Z","contentChangedAt":"2026-09-13T20:32:34.702Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}