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
- Bump the definition's Version (or let the versioning service increment it) before saving instead of reusing an existing version number.
- Load the existing definition by Id and update it, rather than saving a new WorkflowDefinition object with the same definition/version key.
- Delete the conflicting version first if it is truly obsolete, then save the new one.
- Correct the TenantId if the collision is only apparent because definitions were attributed to the wrong tenant.
- 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
- Increment Version for each new version instead of reusing numbers
- Save updates against the existing record's Id rather than inserting duplicates
- Preserve Ids during imports so re-import is an update, not a new record
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
- A secret named ' ' already exists.
- A secret named ' ' already exists.
- 23505
- A already exists with ' ' in tenant ' '.
- A label already exists with normalized name
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)