microsoft/aspire · error · InvalidOperationException

A ConfigureRadiusInfrastructure callback replaced port

Error message

A ConfigureRadiusInfrastructure callback replaced port '{portName}' on container '{mapKey}' with a non-literal Bicep expression. Aspire service discovery already emitted the literal port {expected.Port} into consumer 'services__*' variables and cannot follow a computed port, so a computed containerPort is not supported. Remove the port change.

What it means

During publish, RadiusInfrastructureBuilder validates that ConfigureRadiusInfrastructure callbacks did not alter container ports. Aspire service discovery has already emitted the literal port number into consumers' 'services__*' environment variables, so a port replaced with a computed Bicep expression (or a non-int value) cannot be reconciled. The builder throws this InvalidOperationException to prevent publishing a manifest whose Service discovery would point at a port that differs from the deployed container port.

Solutions

  1. Remove the ConfigureRadiusInfrastructure callback line that changes the container port so the original literal port is kept
  2. If a different port is genuinely required, change the port on the Aspire resource itself (before service discovery values are computed) rather than via the callback
  3. Keep any callback edits limited to properties Aspire does not cross-check, like image or env additions

Example fix

// before
callback: (builder) =>
{
    container.Ports["http"].ContainerPort = BicepValue<int>.Create(myOutput); // computed
}
// after
callback: (builder) =>
{
    // leave the literal ContainerPort set on the resource untouched
}
Defensive patterns

Strategy: validation

Validate before calling

var v = (IBicepValue)port.ContainerPort;
if (v.Expression is not null || v.LiteralValue is not int)
    throw new InvalidOperationException($"Port '{portName}' must keep its literal containerPort.");

Type guard

static bool IsLiteralIntPort(IBicepValue v) => v.Expression is null && v.LiteralValue is int;

Prevention

When it happens

Trigger: A ConfigureRadiusInfrastructure callback assigns port.ContainerPort a BicepValue<int> built from an expression (e.g. an output reference or computed BicepValue) instead of a literal int. The check is triggered when portValueBicep.Expression is not null or LiteralValue is not an int.

Common situations: Developers try to wire a port to a Bicep parameter/output, copy a port from another module's output, or use a BicepValue expression assuming deploy-time resolution works — but service discovery has already burned in the literal value at manifest generation time.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16). Data as JSON: /api/errors/ca1ab0a0b874d42f. Report an issue: GitHub.

Appendix: source

Thrown at src/Aspire.Hosting.Radius/Publishing/RadiusInfrastructureBuilder.cs:4984

            {
                if (!container.Ports.TryGetValue(portName, out var portValue) || portValue.Value is not { } port)
                {
                    throw new InvalidOperationException(
                        $"A ConfigureRadiusInfrastructure callback removed port '{portName}' from container " +
                        $"'{mapKey}'. Aspire service discovery already emitted this port ({expected.Port}) into " +
                        $"consumer 'services__*' variables, so removing it would break cross-container calls. " +
                        $"Remove the port change to keep service discovery consistent.");
                }

                // Reject a non-literal port/protocol: service discovery is a fixed literal, so a
                // callback that swaps in a Bicep expression could evaluate to a different value at
                // deploy time, reintroducing exactly the mismatch this guard prevents. An
                // expression-backed BicepValue<int> reports a default LiteralValue of 0 (not null),
                // so a non-null Expression is the reliable "non-literal" signal, not the LiteralValue.
                var portValueBicep = (IBicepValue)port.ContainerPort;
                if (portValueBicep.Expression is not null || portValueBicep.LiteralValue is not int literalPort)
                {
                    throw new InvalidOperationException(
                        $"A ConfigureRadiusInfrastructure callback replaced port '{portName}' on container " +
                        $"'{mapKey}' with a non-literal Bicep expression. Aspire service discovery already emitted " +
                        $"the literal port {expected.Port} into consumer 'services__*' variables and cannot follow a " +
                        $"computed port, so a computed containerPort is not supported. Remove the port change.");
                }

                var protocolValueBicep = (IBicepValue)port.Protocol;
                if (protocolValueBicep.Expression is not null || protocolValueBicep.LiteralValue is not string literalProtocol)
                {
                    throw new InvalidOperationException(
                        $"A ConfigureRadiusInfrastructure callback replaced the protocol of port '{portName}' on " +
                        $"container '{mapKey}' with a non-literal Bicep expression. Aspire service discovery assumes " +
                        $"the literal protocol '{expected.Protocol}', so a computed protocol is not supported. Remove " +
                        $"the protocol change.");
                }

                if (literalPort != expected.Port || !string.Equals(literalProtocol, expected.Protocol, StringComparison.Ordinal))
                {

View on GitHub (pinned to 25830f84bd)