microsoft/aspire · error · InvalidOperationException
A step with the name
Error message
A step with the name '{step.Name}' has already been added to the pipeline. What it means
AddStep enforces that each PipelineStep in a DistributedApplicationPipeline has a unique name. If a step with the same name (compared as an exact match against already-registered steps) is added a second time, the pipeline throws this InvalidOperationException rather than silently overwriting or duplicating the step, since duplicate names would make dependency resolution ambiguous.
Solutions
- Guard the call: only call AddStep if the step name is not already present, e.g., use pipeline.TryAddStep if available or check _steps/known names first.
- Give each step a unique, descriptive name (e.g., prefix with the owning feature: 'frontend-build' vs 'backend-build').
- Move duplicate-prone registration into an idempotent helper that dedupes by name before adding.
- In tests, create a fresh DistributedApplicationPipeline instance per test instead of reusing one in a shared fixture.
Example fix
// before
pipeline.AddStep(new PipelineStep("build"));
pipeline.AddStep(new PipelineStep("build")); // throws
// after
if (pipeline.Steps.All(s => s.Name != "build"))
{
pipeline.AddStep(new PipelineStep("build"));
} Defensive patterns
Strategy: validation
Validate before calling
if (pipeline.Steps.Any(s => s.Name == step.Name))
{
throw new InvalidOperationException($"Step '{step.Name}' is already registered.");
}
pipeline.AddStep(step); Try / catch
try
{
pipeline.AddStep(step);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("has already been added"))
{
logger.LogWarning("Step {StepName} already registered; skipping duplicate.", step.Name);
} Prevention
- Build registration helpers that dedupe by step name before calling AddStep.
- Use unique, feature-prefixed step names in shared infrastructure code.
- Create a new pipeline instance per test/operation instead of reusing one.
- Keep a single canonical registration site per well-known step.
When it happens
Trigger: Calling pipeline.AddStep(step) when a step whose Name equals step.Name has already been added - e.g., calling AddStep twice for the same step instance, registering the same step in both a base and a derived configuration, or re-running registration code in a loop without unique names.
Common situations: Configuring pipelines in a loop where the step name is not parameterized; calling AddStep from multiple extension methods that each register the same infrastructure step (e.g., both a 'push' and 'deploy' helper adding a 'build' step); test setup methods invoked multiple times on a shared pipeline instance.
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
- Step ' ' is required by unknown step
- Step ' ' not found in pipeline. Available steps
- The requiredBy parameter must be a string or IEnumerable
- Array params contains empty item
- Array params contains null item
AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16).
Data as JSON: /api/errors/69e54607b0e61ba2.
Report an issue: GitHub.
Appendix: source
Thrown at src/Aspire.Hosting/Pipelines/DistributedApplicationPipeline.cs:532
{
foreach (var name in stepNames)
{
step.RequiredBy(name);
}
}
else
{
throw new ArgumentException(
$"The requiredBy parameter must be a string or IEnumerable<string>, but was {requiredBy.GetType().Name}.",
nameof(requiredBy));
}
}
public void AddStep(PipelineStep step)
{
if (_steps.Any(s => s.Name == step.Name))
{
throw new InvalidOperationException(
$"A step with the name '{step.Name}' has already been added to the pipeline.");
}
_steps.Add(step);
}
public void AddPipelineConfiguration(Func<PipelineConfigurationContext, Task> callback)
{
ArgumentNullException.ThrowIfNull(callback);
_configurationCallbacks.Add(callback);
}
/// <summary>
/// Creates a clone of this pipeline whose built-in steps are independent
/// copies (with fresh <see cref="PipelineStep.DependsOnSteps"/> /
/// <see cref="PipelineStep.RequiredBySteps"/> and final action lists).
/// Configuration callbacks are shallow-copied — the same delegates are reused.
/// </summary>View on GitHub (pinned to 25830f84bd)