{"record":{"id":"431d3e950b78d175","repo":"microsoft/aspire","slug":"azure-destroy-step-for-environment-azureenvironment-name-was","errorCode":null,"errorMessage":"Azure destroy step for environment '{azureEnvironment.Name}' was not found.","messagePattern":"Azure destroy step for environment '(.+?)' was not found\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/Aspire.Hosting.Azure.Kubernetes/AzureKubernetesEnvironmentResource.cs","lineNumber":110,"sourceCode":"        {\n            var k8sEnv = KubernetesEnvironment;\n            var getDestroyCredentialsStep = context.GetSteps(this)\n                .Single(step => step.Name == $\"aks-get-credentials-for-destroy-{Name}\");\n            var kubernetesDestroySteps = context\n                .GetSteps(HelmDeploymentEngine.GetKubernetesDestroyTag(k8sEnv.Name))\n                .ToList();\n\n            var deploymentStateManager = context.Services.GetRequiredService<IDeploymentStateManager>();\n            var deploymentStateSection = await deploymentStateManager\n                .AcquireSectionAsync($\"Azure:Deployments:{Name}\")\n                .ConfigureAwait(false);\n\n            var azureEnvironment = context.Model.Resources.OfType<AzureEnvironmentResource>().SingleOrDefault()\n                ?? throw new InvalidOperationException(\n                    $\"Azure environment resource required by AKS environment '{Name}' was not found.\");\n            var destroyAzureStep = context.GetSteps(azureEnvironment)\n                .SingleOrDefault(step => step.Name == $\"destroy-azure-{azureEnvironment.Name}\")\n                ?? throw new InvalidOperationException(\n                    $\"Azure destroy step for environment '{azureEnvironment.Name}' was not found.\");\n\n            // A never-deployed AKS environment has no isolated kubeconfig to acquire. Likewise, a\n            // partially deployed environment can persist the cluster ID before any Helm release saves\n            // destroy state. In either case, aggregate Azure cleanup must skip cluster-scoped destroy\n            // steps rather than block on reacquiring credentials when there is nothing known to clean\n            // up. Explicitly targeting one of those Kubernetes cleanup steps still runs through the\n            // credential prerequisite and fails rather than allowing the command to fall back to the\n            // caller's ambient Kubernetes context.\n            var targetStep = context.Services.GetRequiredService<IOptions<PipelineOptions>>().Value.Step;\n            var hasPersistedAksIdentity = HasPersistedAksIdentity(deploymentStateSection.Data);\n            var hasPersistedKubernetesCleanupState = hasPersistedAksIdentity &&\n                await HasPersistedKubernetesCleanupStateAsync(\n                    deploymentStateManager,\n                    context.Model,\n                    k8sEnv).ConfigureAwait(false);\n\n            if (!hasPersistedAksIdentity || !hasPersistedKubernetesCleanupState)","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Hosting.Azure.Kubernetes/AzureKubernetesEnvironmentResource.cs#L92-L128","documentation":"Having found the AzureEnvironmentResource, the AKS environment locates its sibling Azure destroy step (named 'destroy-azure-<name>') so cluster-scoped cleanup can be sequenced with/after aggregate Azure cleanup. If that step is not registered on the Azure environment resource, the expected pipeline contract is violated and Aspire throws rather than skipping Azure destroy silently.","triggerScenarios":"Executing AKS destroy steps when context.GetSteps(azureEnvironment) contains zero or multiple steps named 'destroy-azure-<azureEnvironment.Name>' - e.g. a customized/overridden Azure environment pipeline that removed or renamed the destroy step, or duplicate-step registration breaking SingleOrDefault.","commonSituations":"Custom pipeline transformations that filter or rename Azure steps; using an older/newer Azure hosting package where step naming changed relative to the AKS package; intercepting events to strip destroy steps in publish-only setups.","solutions":["Ensure the default Azure environment pipeline is intact so its 'destroy-azure-<name>' step is registered; remove customizations that drop or rename it.","Verify the step name matches exactly ('destroy-azure-' + azureEnvironment.Name) if creating steps manually.","Align Azure.Kubernetes and Azure hosting package versions so both agree on step naming conventions.","Check eventing/interception code that mutates GetSteps output for the Azure environment."],"exampleFix":"// before\n// custom step filtering removed the destroy step\nsteps.RemoveAll(s => s.Name.StartsWith(\"destroy-azure\"));\n\n// after\n// keep the destroy step; only reorder around it\nvar destroy = steps.Single(s => s.Name == $\"destroy-azure-{azure.Name}\");\n// schedule other steps relative to destroy","handlingStrategy":"type-guard","validationCode":"var destroyStep = context.GetSteps(azureEnvironment)\n    .SingleOrDefault(s => s.Name == $\"destroy-azure-{azureEnvironment.Name}\");\nif (destroyStep is null)\n    throw new InvalidOperationException(\"Azure destroy step missing; ensure the default Azure pipeline is unmodified.\");","typeGuard":"bool HasAzureDestroyStep(IResource azureEnv, string stepName) =>\n    context.GetSteps(azureEnv).Any(s => s.Name == stepName);","tryCatchPattern":null,"preventionTips":["Do not remove or rename default Azure environment steps in custom pipelines","Keep Azure.Kubernetes and Azure hosting packages version-aligned","Test destroy mode after any pipeline customization"],"tags":["azure","aks","destroy","pipeline-steps"],"backgroundTag":"internal-invariant-violation","analyzedSha":"25830f84bd145686607ad00c057b3f84e2e51d43","analyzedAt":"2026-09-16T11:10:06.193Z","contentChangedAt":"2026-09-16T11:10:06.193Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}