microsoft/aspire · critical · InvalidOperationException

The new Azure sandbox deployment

Error message

The new Azure sandbox deployment '{resource.Name}' succeeded, but the previous generation could not be removed after a security-relevant endpoint change. The new deployment state was preserved, but the deployment is reported as failed so the older endpoint is not silently treated as secured.

What it means

After a successful new sandbox deployment, Aspire prunes the previous generation as a best-effort step. If pruning fails AND the deployment involved a security-relevant endpoint change, the method rethrows as InvalidOperationException so the failure is visible; otherwise the old (possibly insecure) endpoint would remain reachable while the deployment reports success.

Solutions

  1. Inspect the inner exception (logged as a warning) to see why pruning failed — usually RBAC permissions or a transient ARM error — and fix that.
  2. Manually delete the old generation/endpoint in the Azure portal, then re-run the deployment.
  3. Verify the service principal/contributor has delete permissions on the sandbox resource group; retry the deployment after permissions are granted.
Defensive patterns

Strategy: try-catch

Validate before calling

// before deploying with an endpoint security change, verify delete rights:
// check role assignments include Contributor/Owner on the sandbox resource group

Try / catch

try { await deployment.DeployAsync(...); }
catch (InvalidOperationException ex) when (ex.Message.Contains("previous generation could not be removed"))
{
    // new deployment is ACTIVE despite failure report; inspect inner ex,
    // manually remove the old generation/endpoint, then redeploy or verify manually
}

Prevention

When it happens

Trigger: DeployAsync succeeds and creates the new deployment, then the best-effort cleanup of the prior generation throws (e.g. Azure API error deleting the old deployment), while securityConfigurationChanged is true because an endpoint security setting changed between generations.

Common situations: Rotating an endpoint from public to secured (or changing auth) and hitting a transient Azure API failure or permission gap when deleting the old deployment generation.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at src/Aspire.Hosting.Azure.Sandboxes/AzureSandboxContainerDeployment.cs:490

                if (securityConfigurationChanged ||
                    pendingOwnerCleanupIds.Count > 0 ||
                    pendingLegacyDeploymentCleanup is not null)
                {
                    stateSection.Data["PendingSecurityCleanup"] = false;
                    stateSection.Data.Remove("PendingOwnerCleanupIds");
                    stateSection.Data.Remove("PendingLegacyDeploymentCleanup");
                    await deploymentStateManager.SaveSectionAsync(stateSection, context.CancellationToken).ConfigureAwait(false);
                }
            }
            catch (Exception ex) when (ex is not OperationCanceledException)
            {
                context.Logger.LogWarning(
                    ex,
                    "Best-effort pruning failed after Azure sandbox deployment '{ResourceName}' completed. The new deployment remains active and its state was preserved.",
                    resource.Name);
                if (securityConfigurationChanged)
                {
                    throw new InvalidOperationException(
                        $"The new Azure sandbox deployment '{resource.Name}' succeeded, but the previous generation could not be removed after a security-relevant endpoint change. The new deployment state was preserved, but the deployment is reported as failed so the older endpoint is not silently treated as secured.",
                        ex);
                }
            }

            if (portStates.FirstOrDefault() is JsonObject firstPort && firstPort["Url"]?.GetValue<string>() is { } publicUrl)
            {
                var retainedUrl = securityConfigurationChanged || ownerChanged
                    ? null
                    : GetFirstStateUrl(previousStateSection);
                context.Summary.Add(resource.TargetResource.Name, new MarkdownString(CreateSandboxUrlSummary(publicUrl, retainedUrl)));
            }
            else
            {
                context.Summary.Add(resource.TargetResource.Name, new MarkdownString($"Sandbox `{sandboxId}`"));
            }
        }
        catch

View on GitHub (pinned to 25830f84bd)