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
- Create all tasks before completing the step; restructure so completion is the last operation.
- Check step.CompletionState == CompletionState.InProgress before calling CreateTaskAsync and skip/report accordingly.
- Serialize work: await task creation before calling CompleteStepAsync instead of fire-and-forget.
- 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
- Complete the step only after all task creation and updates finish
- Avoid fire-and-forget work that outlives step completion
- Use try/finally so error paths still create diagnostics tasks before completing
- Serialize step completion and task creation on one logical flow
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
- Cannot complete task
- Cannot update task ' ' because its parent step ' ' is…
- Parent step with ID ' ' does not exist.
- Step with ID ' ' does not exist.
- The MAUI OTLP dev tunnel configuration was not initialized…
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 = enableMarkdownView on GitHub (pinned to 25830f84bd)