elsa-workflows/elsa-core · error · InvalidOperationException
Duplicate ingress source registration for name
Error message
Duplicate ingress source registration for name '{source.Name}'. What it means
IngressSourceRegistry.EnsureMaterialized lazily builds entries from the configured sources factory and requires each source to have a unique name. When two or more registered ingress sources share the same name, TryAdd fails and InvalidOperationException is thrown. This is a configuration invariant protecting name-based lookup of ingress sources.
Solutions
- Find the duplicate registration and give each ingress source a distinct Name.
- Guard registration code so the same source is not added twice (e.g. check if already registered before adding).
- Review DI setup for duplicate AddIngressSource-style calls across feature modules.
- Add a startup-time uniqueness check for source names to fail fast with a clearer message.
Example fix
// before
options.AddSource(new ConsoleIngressSource { Name = "console" });
options.AddSource(new FileIngressSource { Name = "console" }); // duplicate
// after
options.AddSource(new ConsoleIngressSource { Name = "console" });
options.AddSource(new FileIngressSource { Name = "console-file" }); Defensive patterns
Strategy: validation
Validate before calling
var names = options.Sources.Select(s => s.Name).ToList();
var duplicates = names.GroupBy(n => n).Where(g => g.Count() > 1).ToList();
if (duplicates.Any()) throw new InvalidOperationException($"Duplicate ingress source names: {string.Join(", ", duplicates.Select(d => d.Key))}"); Try / catch
try
{
_ = registry.Sources;
}
catch (InvalidOperationException ex) when (ex.Message.StartsWith("Duplicate ingress source registration"))
{
logger.LogCritical(ex, "Ingress source names must be unique.");
} Prevention
- Give every ingress source a unique, explicit Name
- Guard against duplicate Add calls in DI setup
- Validate source name uniqueness at startup
When it happens
Trigger: Registering two or more ingress sources with the same Name via the diagnostics console-logs ingress source registration, then first accessing Sources, Snapshot, MarkPauseFailedAsync, RecordTransition, or GetState, which triggers materialization.
Common situations: Two features both registering a source with a default name (e.g. 'console'); copying registration code without renaming the source; duplicate AddX calls in DI configuration.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- A configuration key is required.
- A configured default role no longer exists.
- A workflow definition handle must be provided.
- A workflow definition or artifact-relative path is required.
- Adapter ' ' does not provide a settings migration from…
AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13).
Data as JSON: /api/errors/609ecaca094fa13f.
Report an issue: GitHub.
Appendix: source
Thrown at src/modules/Elsa.Workflows.Runtime/Services/IngressSourceRegistry.cs:55
return _entries.Values.Select(e => e.Source).ToArray();
}
}
private void EnsureMaterialized()
{
// Double-checked lock: a `_entries.Count > 0` guard is not atomic with the population loop, so concurrent
// first callers could both observe an empty dictionary, both iterate _sourcesFactory.Value, and both call
// TryAdd — losing the duplicate-name guarantee and crashing the second caller's execution cycle start with
// "Duplicate ingress source registration". A short lock around the one-time population is sufficient;
// steady-state reads short-circuit on the volatile flag without taking the lock.
if (_materialized) return;
lock (_materializeSync)
{
if (_materialized) return;
foreach (var source in _sourcesFactory.Value)
{
if (!_entries.TryAdd(source.Name, new Entry(source)))
throw new InvalidOperationException($"Duplicate ingress source registration for name '{source.Name}'.");
}
_materialized = true;
}
}
/// <inheritdoc />
public IReadOnlyCollection<IngressSourceSnapshot> Snapshot()
{
EnsureMaterialized();
return _entries.Values
.Select(e => new IngressSourceSnapshot(e.Source.Name, e.State, e.LastError, e.LastTransitionAt))
.ToArray();
}
/// <inheritdoc />
public ValueTask MarkPauseFailedAsync(string name, string reason, Exception? error = null)
{
EnsureMaterialized();View on GitHub (pinned to fe9217bdfa)