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

  1. 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.
  2. Give each step a unique, descriptive name (e.g., prefix with the owning feature: 'frontend-build' vs 'backend-build').
  3. Move duplicate-prone registration into an idempotent helper that dedupes by name before adding.
  4. 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

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


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)