microsoft/aspire · error · InvalidOperationException

Step ' ' not found in pipeline. Available steps

Error message

Step '{stepName}' not found in pipeline. Available steps: {availableSteps}

What it means

When executing a single step sequentially (ExecuteStepSequentiallyAsync), the pipeline resolves the requested stepName against all registered steps using an ordinal dictionary. If the name does not match any step, it throws this InvalidOperationException and helpfully lists the available step names so the caller can see what went wrong. It is a lookup failure, not a state corruption.

Solutions

  1. Compare stepName against the available steps list printed in the exception message and correct the spelling/casing.
  2. Enumerate registered steps (pipeline.Steps or equivalent) at runtime and pick the name programmatically instead of hardcoding it.
  3. Ensure the step is added before execution: verify AddStep was called for the target step on this pipeline instance.
  4. Normalize name comparison: pass exactly the step.Name value rather than a re-typed literal.

Example fix

// before
await pipeline.ExecuteStepSequentiallyAsync(context, "depoy"); // typo
// after
var stepName = pipeline.Steps.First(s => s.Name == "deploy").Name;
await pipeline.ExecuteStepSequentiallyAsync(context, stepName);
Defensive patterns

Strategy: validation

Validate before calling

var known = pipeline.Steps.Select(s => s.Name).ToHashSet(StringComparer.Ordinal);
if (!known.Contains(stepName))
{
    throw new InvalidOperationException($"'{stepName}' is not a registered step. Known: {string.Join(", ", known)}");
}

Try / catch

try
{
    await pipeline.ExecuteStepSequentiallyAsync(context, stepName);
}
catch (InvalidOperationException ex) when (ex.Message.StartsWith("Step '") && ex.Message.Contains("not found in pipeline"))
{
    logger.LogError(ex, "Unknown step '{StepName}'. Register it or fix the name.", stepName);
}

Prevention

When it happens

Trigger: Calling the pipeline execution overload that targets a specific step name with a name that is misspelled, differs in casing, contains extra whitespace, or refers to a step that was never added (or was added under a different name).

Common situations: Running 'aspire publish/run --step <name>' style targeting from a script or CLI with a stale step name; renaming a step in code but not updating the caller; case-sensitivity confusion ('Deploy' vs 'deploy'); typos in test code invoking ExecuteStepSequentiallyAsync.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16). Data as JSON: /api/errors/64f75f78ba47a95a. Report an issue: GitHub.

Appendix: source

Thrown at src/Aspire.Hosting/Pipelines/DistributedApplicationPipeline.cs:608

    /// <param name="context">The pipeline context for execution.</param>
    /// <returns>A task representing the asynchronous operation.</returns>
    internal async Task ExecuteStepSequentiallyAsync(
        string stepName,
        PipelineContext context)
    {
        var allSteps = await ResolveStepsAsync(context).ConfigureAwait(false);

        if (allSteps.Count == 0)
        {
            return;
        }

        var allStepsByName = allSteps.ToDictionary(s => s.Name, StringComparer.Ordinal);

        if (!allStepsByName.TryGetValue(stepName, out var targetStep))
        {
            var availableSteps = string.Join(", ", allSteps.Select(s => $"'{s.Name}'"));
            throw new InvalidOperationException(
                $"Step '{stepName}' not found in pipeline. Available steps: {availableSteps}");
        }

        var stepsToExecute = ComputeTransitiveDependencies(targetStep, allStepsByName);

        await ExecuteStepsSequentially(stepsToExecute, context).ConfigureAwait(false);
    }

    /// <summary>
    /// Resolves all pipeline steps (from built-in steps and resource annotations),
    /// normalizes RequiredBy relationships to DependsOn, and validates the steps
    /// without executing them. The returned list is in collection order; use
    /// <see cref="GetTopologicalOrder"/> to obtain execution order.
    /// </summary>
    internal async Task<List<PipelineStep>> ResolveStepsAsync(PipelineContext context)
    {
        var annotationSteps = await CollectStepsFromAnnotationsAsync(context).ConfigureAwait(false);
        // Configuration callbacks are run on every resolution, so give them fresh built-in steps instead

View on GitHub (pinned to 25830f84bd)