{"record":{"id":"f46f15d19add9fe5","repo":"microsoft/aspire","slug":"a-configureradiusinfrastructure-callback-removed-or-replaced","errorCode":null,"errorMessage":"A ConfigureRadiusInfrastructure callback removed or replaced container '{mapKey}'. Aspire service discovery already emitted 'services__*' variables that address it, so dropping the workload would break cross-container calls. Keep the container to keep service discovery consistent.","messagePattern":"A ConfigureRadiusInfrastructure callback removed or replaced container '(.+?)'\\. Aspire service discovery already emitted 'services__\\*' variables that address it, so dropping the workload would break cross-container calls\\. Keep the container to keep service discovery consistent\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Aspire.Hosting.Radius/Publishing/RadiusInfrastructureBuilder.cs","lineNumber":4952,"sourceCode":"            containersByMapKey[container.ContainerMapKey] = container;\n        }\n\n        foreach (var (mapKey, snapshot) in portSnapshots)\n        {\n            // A portless container has no Service and no `services__*` value can address it, so\n            // removing it in a callback is harmless — skip the preservation check for empty\n            // snapshots so the invariant does not needlessly reject valid customization callbacks.\n            if (snapshot.Count == 0)\n            {\n                continue;\n            }\n\n            // The workload service discovery was emitted for must still be present under the same\n            // map key. A callback that removed it — or replaced it with a differently keyed\n            // container — leaves consumers pointing at a Service that is no longer produced.\n            if (!containersByMapKey.TryGetValue(mapKey, out var container))\n            {\n                throw new InvalidOperationException(\n                    $\"A ConfigureRadiusInfrastructure callback removed or replaced container '{mapKey}'. Aspire \" +\n                    $\"service discovery already emitted 'services__*' variables that address it, so dropping the \" +\n                    $\"workload would break cross-container calls. Keep the container to keep service discovery consistent.\");\n            }\n\n            // Only containers that had service ports pre-callback have a Service (`{name}-{name}`)\n            // that `services__*` addresses, so the name/map-key equality is only required for them.\n            // A portless baseline container or one added entirely by the callback has no service-\n            // discovery contract — Radius permits its top-level name to differ from the map key — so\n            // gating this check on a non-empty snapshot keeps the customization escape hatch open.\n            ValidateContainerNameMatchesMapKey(container);\n\n            foreach (var (portName, expected) in snapshot)\n            {\n                if (!container.Ports.TryGetValue(portName, out var portValue) || portValue.Value is not { } port)\n                {\n                    throw new InvalidOperationException(\n                        $\"A ConfigureRadiusInfrastructure callback removed port '{portName}' from container \" +","sourceCodeStart":4934,"sourceCodeEnd":4970,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Hosting.Radius/Publishing/RadiusInfrastructureBuilder.cs#L4934-L4970","documentation":"Aspire has already emitted services__* service-discovery environment variables pointing at a container workload. If a ConfigureRadiusInfrastructure callback removed or replaced that container (no longer present under its map key), consumers would call a Service that is never produced, so the publisher throws InvalidOperationException.","triggerScenarios":"A ConfigureRadiusInfrastructure callback removes a container, or replaces it with a container keyed differently, after service discovery variables were emitted; validation finds mapKey missing in containersByMapKey.","commonSituations":"Callback pruning 'unused' workloads that are actually discovery targets; rebuilding containers under new names in callbacks; renaming resources after discovery was computed.","solutions":["Keep the container under the same name/map key in the callback.","If the workload is truly gone, also remove/adjust the referencing services__* discovery variables and the consumers.","Replace the resource in the app model (not the callback) so discovery is computed for the final set.","Validate the final container set matches the pre-callback snapshot before publishing."],"exampleFix":"// before\ninfra.Callback(c => c.Resources.Remove(frontend)); // consumers use services__frontend__https\n// after\ninfra.Callback(c => { /* keep frontend */ });","handlingStrategy":"validation","validationCode":"// After callbacks, verify discovery targets still exist under their original names\nforeach (var target in discoveryTargets)\n    if (!finalContainers.Any(c => c.Name == target.Name))\n        throw new InvalidOperationException($\"Callback removed container '{target.Name}' required by services__* variables.\");","typeGuard":null,"tryCatchPattern":"try { PublishAsync(model); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"ConfigureRadiusInfrastructure callback removed or replaced container\"))\n{ log.LogError(ex, \"Restore container '{Container}' or update consumers.\", ex.Data[\"mapKey\"]); }","preventionTips":["Do not remove or rename containers inside ConfigureRadiusInfrastructure callbacks once discovery is emitted.","Perform workload removal/restructuring in the AppHost model before publishing.","Keep container names stable across refactors.","Test published output whenever callbacks mutate the resource set."],"tags":["radius","callbacks","service-discovery","publishing"],"backgroundTag":"invalid-state-transition","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"}