microsoft/aspire · error · InvalidOperationException

Azure deployment for

Error message

Azure deployment for {resource.Name} was canceled.

What it means

When reconciling a Bicep deployment, Aspire reads the ARM deployment's ProvisioningState. If ARM reports the deployment was Canceled, Aspire persists that terminal state, publishes an 'Azure deployment canceled' state to the dashboard, and throws InvalidOperationException so the resource does not silently re-enter a retry loop. This makes ARM's terminal cancellation visible instead of looping indefinitely.

Solutions

  1. Check the deployment in the Azure portal (Resource Group > Deployments) to see which operation was canceled and why.
  2. Delete or clean up the canceled deployment / resource group state, then re-run the app host so a fresh deployment is created.
  3. If you canceled it intentionally (Ctrl+C during provisioning), simply re-run — but ensure no stale conflicting deployment remains.
  4. Look for concurrent processes deploying to the same resource group and serialize them; overlapping deployments can cancel each other.
  5. If cancellation is spurious, inspect activity logs for the caller that issued the cancel request.

Example fix

// before
dotnet run --project MyAspireApp.AppHost   # repeatedly hits canceled deployment

// after
az group deployment list -g my-rg -o table          # find the canceled deployment
az deployment group delete -g my-rg --name <stale-deployment>
dotnet run --project MyAspireApp.AppHost             # fresh deployment succeeds
Defensive patterns

Strategy: try-catch

Validate before calling

var deployment = await armClient.GetArmResource(deploymentId).GetAsync(ct);
if (string.Equals(deployment.Value.Data.ProvisioningState, "Canceled", StringComparison.OrdinalIgnoreCase))
{
    // reconcile stale deployment state before re-running provisioning
}

Try / catch

try
{
    await provisioner.GetOrCreateResourceAsync(resource, context, ct);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("was canceled"))
{
    logger.LogWarning(ex, "Azure deployment was canceled (ARM terminal state). Check portal deployments, clean up, and re-run.");
}

Prevention

When it happens

Trigger: Calling GetOrCreateResourceAsync / ReconcileDeploymentStateAsync (or TryAdoptActiveDeploymentConflictAsync when adopting an existing active deployment) against a deployment whose latest ARM provisioning state is 'Canceled' — typically because someone or another process canceled the deployment in the Azure portal, or a Ctrl+C/timeout canceled it mid-provisioning.

Common situations: A developer canceled a deployment in the Azure portal or via CLI; an upstream ARM timeout or parallel conflicting deployment caused cancellation; a previous run of the app host was terminated and its deployment canceled, and a new run now reconciles against that canceled deployment.

Related errors


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

Appendix: source

Thrown at src/Aspire.Hosting.Azure/Provisioning/Provisioners/BicepProvisioner.cs:424

        {
            // Successful adoption replays the deployment outputs into the resource
            // state exactly as normal provisioning would.
            return await ConfigureSucceededReconciledDeploymentAsync(
                resource,
                stateSection,
                deploymentId,
                deployment.Outputs,
                currentLocation,
                cancellationToken).ConfigureAwait(false);
        }

        if (string.Equals(deployment.ProvisioningState, DeploymentStateProvisioningStateCanceled, StringComparisons.AzureProvisioningState))
        {
            // Preserve ARM's terminal cancellation so the dashboard explains why this
            // resource stopped instead of silently retrying.
            await PersistReconciledProvisioningStateAsync(stateSection, DeploymentStateProvisioningStateCanceled, cancellationToken).ConfigureAwait(false);
            await PublishReconciledTerminalStateAsync(resource, AzureProvisioningStrings.ResourceStateAzureDeploymentCanceled).ConfigureAwait(false);
            throw new InvalidOperationException($"Azure deployment for {resource.Name} was canceled.");
        }

        if (string.Equals(deployment.ProvisioningState, DeploymentStateProvisioningStateFailed, StringComparisons.AzureProvisioningState))
        {
            // Preserve ARM's terminal failure for the same reason as cancellation:
            // this is the outcome of the adopted deployment.
            await PersistReconciledProvisioningStateAsync(stateSection, DeploymentStateProvisioningStateFailed, cancellationToken).ConfigureAwait(false);
            await PublishReconciledTerminalStateAsync(resource, AzureProvisioningStrings.ResourceStateAzureDeploymentFailed).ConfigureAwait(false);
            throw new InvalidOperationException($"Azure deployment for {resource.Name} failed.");
        }

        return false;
    }

    private async Task PersistReconciledProvisioningStateAsync(DeploymentStateSection stateSection, string provisioningState, CancellationToken cancellationToken)
    {
        stateSection.Data[DeploymentStateProvisioningStateKey] = provisioningState;
        await deploymentStateManager.SaveSectionAsync(stateSection, cancellationToken).ConfigureAwait(false);

View on GitHub (pinned to 25830f84bd)