microsoft/aspire · error · InvalidOperationException

Cannot create task for step

Error message

Cannot create task for step '{step.Id}' because the step is already complete.

What it means

After locating the parent step, CreateTaskAsync checks the step's CompletionState under a lock. A task can only be attached to a step that is still InProgress; if the step already completed (Completed/WithError/WithWarning), the reporter throws InvalidOperationException because a late task could no longer affect the reported outcome.

Solutions

  1. Create all tasks before completing the step; restructure so completion is the last operation.
  2. Check step.CompletionState == CompletionState.InProgress before calling CreateTaskAsync and skip/report accordingly.
  3. Serialize work: await task creation before calling CompleteStepAsync instead of fire-and-forget.
  4. Wrap in try-catch on InvalidOperationException if late task creation is benign in your flow.

Example fix

// before
await step.CompleteAsync();
await reporter.CreateTaskAsync(step, text, false); // throws
// after
await reporter.CreateTaskAsync(step, text, false);
await step.CompleteAsync();
Defensive patterns

Strategy: validation

Validate before calling

if (step.CompletionState != CompletionState.InProgress)
    return; // or log — cannot attach tasks to a completed step

Type guard

bool CanAttachTask(ReportingStep step) => step.CompletionState == CompletionState.InProgress;

Try / catch

try { await reporter.CreateTaskAsync(step, text, false); } catch (InvalidOperationException ex) when (ex.Message.Contains("already complete")) { logger.LogDebug("Step completed before task creation; skipping."); }

Prevention

When it happens

Trigger: Calling CreateTaskAsync for a step after CompleteStepAsync (or equivalent completion) has run on it — e.g. firing concurrent tasks after awaiting step completion, or retrying task creation after a failure path already completed the step.

Common situations: Parallel pipeline callbacks where one callback completes the step while another still tries to add a task; a step completed in an error path before logging tasks are created; tests running steps to completion before asserting task output.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at src/Aspire.Hosting/Pipelines/PipelineActivityReporter.cs:93

            Data = CreateStepActivityData(step, step.Title, CompletionState.InProgress, enableMarkdown: false)
        };

        await ActivityItemUpdated.Writer.WriteAsync(state, cancellationToken).ConfigureAwait(false);
        return step;
    }

    public async Task<ReportingTask> CreateTaskAsync(ReportingStep step, string statusText, bool enableMarkdown, CancellationToken cancellationToken)
    {
        if (!_steps.TryGetValue(step.Id, out var parentStep))
        {
            throw new InvalidOperationException($"Step with ID '{step.Id}' does not exist.");
        }

        lock (parentStep)
        {
            if (parentStep.CompletionState != CompletionState.InProgress)
            {
                throw new InvalidOperationException($"Cannot create task for step '{step.Id}' because the step is already complete.");
            }
        }

        var task = new ReportingTask(Guid.NewGuid().ToString(), step.Id, statusText, parentStep);

        // Add task to parent step
        parentStep.AddTask(task);

        var state = new PublishingActivity
        {
            Type = PublishingActivityTypes.Task,
            Data = new PublishingActivityData
            {
                Id = task.Id,
                StatusText = statusText,
                CompletionState = ToBackchannelCompletionState(CompletionState.InProgress),
                StepId = step.Id,
                EnableMarkdown = enableMarkdown

View on GitHub (pinned to 25830f84bd)