elsa-workflows/elsa-core · error · InvalidOperationException

A workflow definition already exists for definition

Error message

A workflow definition already exists for definition '{definition.DefinitionId}' version {definition.Version} tenant '{definition.TenantId}'.

What it means

The in-memory workflow definition store enforces uniqueness of (DefinitionId, Version, TenantId) across separately-stored definitions. Before saving, EnsureVersionKeyAvailable checks that no other stored definition (different Id) occupies the same version key; if one does, saving would silently shadow a version, so a conflict exception is thrown.

Solutions

  1. Bump the definition's Version (or let the versioning service increment it) before saving instead of reusing an existing version number.
  2. Load the existing definition by Id and update it, rather than saving a new WorkflowDefinition object with the same definition/version key.
  3. Delete the conflicting version first if it is truly obsolete, then save the new one.
  4. Correct the TenantId if the collision is only apparent because definitions were attributed to the wrong tenant.
  5. Generate and reuse a stable Id per definition version on import so updates map to updates, not inserts.

Example fix

// before
var copy = existing with { Id = Guid.NewGuid().ToString() }; // same DefinitionId+Version
await store.SaveAsync(copy); // conflict
// after
var copy = existing with { Id = Guid.NewGuid().ToString(), Version = existing.Version + 1 };
await store.SaveAsync(copy);
Defensive patterns

Strategy: validation

Validate before calling

var existing = await store.FindAsync(new WorkflowDefinitionFilter { DefinitionId = definition.DefinitionId, Version = definition.Version });
if (existing != null && existing.Id != definition.Id) throw new InvalidOperationException("Version key already used by a different record; bump Version or update the existing record");

Try / catch

try { await store.SaveAsync(definition); } catch (Exception ex) { logger.LogError(ex, "Version conflict for {DefinitionId} v{Version}", definition.DefinitionId, definition.Version); }

Prevention

When it happens

Trigger: Calling SaveAsync (or SaveManyAsync) with a WorkflowDefinition whose Id differs from an existing stored item but whose (DefinitionId, Version, TenantId) triple matches that existing item — e.g. re-importing a definition version with a fresh Id.

Common situations: Re-publishing/importing workflow definitions without bumping Version; duplicate imports from JSON/JSON files with regenerated Ids; multi-tenant data saved with the wrong TenantId; concurrent saves of the same version from two hosts sharing the in-memory store in dev.

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

Appendix: source

Thrown at src/modules/Elsa.Workflows.Management/Stores/MemoryWorkflowDefinitionStore.cs:217

    /// </remarks>
    private IQueryable<WorkflowDefinition> Filter(IQueryable<WorkflowDefinition> queryable, WorkflowDefinitionFilter filter) =>
        filter.Apply(queryable.WhereVisibleToTenant(CurrentTenantId, filter.TenantAgnostic));

    private string CurrentTenantId => tenantAccessor?.TenantId ?? Tenant.DefaultTenantId;

    /// <remarks>
    /// EF enforces <c>(DefinitionId, Version)</c> globally via
    /// <c>IX_WorkflowDefinition_DefinitionId_Version</c>. Memory keeps tenant in the key so
    /// same-tenant duplicates fail closed while cross-tenant rows remain distinct until #7539
    /// adds <c>TenantId</c> to that index.
    /// </remarks>
    private void EnsureVersionKeyAvailable(WorkflowDefinition definition)
    {
        var versionKey = GetVersionKey(definition);
        var existing = store.Find(x => x.Id != definition.Id && GetVersionKey(x) == versionKey);

        if (existing is not null)
            throw CreateVersionKeyConflict(definition);
    }

    private static void EnsureBatchVersionKeysUnique(IEnumerable<WorkflowDefinition> definitions)
    {
        var seen = new Dictionary<DefinitionVersionKey, string>();

        foreach (var definition in definitions)
        {
            var versionKey = GetVersionKey(definition);

            if (seen.TryGetValue(versionKey, out var existingId) && existingId != definition.Id)
                throw CreateVersionKeyConflict(definition);

            seen[versionKey] = definition.Id;
        }
    }

    private static InvalidOperationException CreateVersionKeyConflict(WorkflowDefinition definition) =>

View on GitHub (pinned to fe9217bdfa)