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
- Inspect the inner exception (logged as a warning) to see why pruning failed — usually RBAC permissions or a transient ARM error — and fix that.
- Manually delete the old generation/endpoint in the Azure portal, then re-run the deployment.
- 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
- Grant the deploying identity delete permissions on the resource group before rotating endpoint security.
- After changing endpoint security settings, monitor deployments closely and check for leftover old endpoints in the portal.
- Read the inner exception — pruning failures are usually transient ARM errors or RBAC gaps.
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
- Azure sandbox resource
- ADC request ' ' returned an incomplete response.
- Azure deployment for
- Azure deployment for
- Azure sandbox group ' ' uses identity ' ' for both image…
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}`"));
}
}
catchView on GitHub (pinned to 25830f84bd)