microsoft/aspire · error · InvalidOperationException
A ConfigureRadiusInfrastructure callback removed or…
Error message
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. What it means
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.
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.
Example fix
// before
infra.Callback(c => c.Resources.Remove(frontend)); // consumers use services__frontend__https
// after
infra.Callback(c => { /* keep frontend */ }); Defensive patterns
Strategy: validation
Validate before calling
// After callbacks, verify discovery targets still exist under their original names
foreach (var target in discoveryTargets)
if (!finalContainers.Any(c => c.Name == target.Name))
throw new InvalidOperationException($"Callback removed container '{target.Name}' required by services__* variables."); Try / catch
try { PublishAsync(model); }
catch (InvalidOperationException ex) when (ex.Message.Contains("ConfigureRadiusInfrastructure callback removed or replaced container"))
{ log.LogError(ex, "Restore container '{Container}' or update consumers.", ex.Data["mapKey"]); } Prevention
- 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.
When it happens
Trigger: 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.
Common situations: Callback pruning 'unused' workloads that are actually discovery targets; rebuilding containers under new names in callbacks; renaming resources after discovery was computed.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- A ConfigureRadiusInfrastructure callback removed port
- A ConfigureRadiusInfrastructure callback changed port
- A ConfigureRadiusInfrastructure callback renamed container
- A ConfigureRadiusInfrastructure callback replaced container
- A ConfigureRadiusInfrastructure callback replaced port
AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16).
Data as JSON: /api/errors/f46f15d19add9fe5.
Report an issue: GitHub.
Appendix: source
Thrown at src/Aspire.Hosting.Radius/Publishing/RadiusInfrastructureBuilder.cs:4952
containersByMapKey[container.ContainerMapKey] = container;
}
foreach (var (mapKey, snapshot) in portSnapshots)
{
// A portless container has no Service and no `services__*` value can address it, so
// removing it in a callback is harmless — skip the preservation check for empty
// snapshots so the invariant does not needlessly reject valid customization callbacks.
if (snapshot.Count == 0)
{
continue;
}
// The workload service discovery was emitted for must still be present under the same
// map key. A callback that removed it — or replaced it with a differently keyed
// container — leaves consumers pointing at a Service that is no longer produced.
if (!containersByMapKey.TryGetValue(mapKey, out var container))
{
throw new InvalidOperationException(
$"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.");
}
// Only containers that had service ports pre-callback have a Service (`{name}-{name}`)
// that `services__*` addresses, so the name/map-key equality is only required for them.
// A portless baseline container or one added entirely by the callback has no service-
// discovery contract — Radius permits its top-level name to differ from the map key — so
// gating this check on a non-empty snapshot keeps the customization escape hatch open.
ValidateContainerNameMatchesMapKey(container);
foreach (var (portName, expected) in snapshot)
{
if (!container.Ports.TryGetValue(portName, out var portValue) || portValue.Value is not { } port)
{
throw new InvalidOperationException(
$"A ConfigureRadiusInfrastructure callback removed port '{portName}' from container " +View on GitHub (pinned to 25830f84bd)