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

  1. Keep the container under the same name/map key in the callback.
  2. If the workload is truly gone, also remove/adjust the referencing services__* discovery variables and the consumers.
  3. Replace the resource in the app model (not the callback) so discovery is computed for the final set.
  4. 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

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


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)