{"record":{"id":"ca1ab0a0b874d42f","repo":"microsoft/aspire","slug":"a-configureradiusinfrastructure-callback-replaced-port","errorCode":null,"errorMessage":"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.","messagePattern":"A ConfigureRadiusInfrastructure callback replaced port '(.+?)' on container '(.+?)' with a non-literal Bicep expression\\. Aspire service discovery already emitted the literal port (.+?) into consumer 'services__\\*' variables and cannot follow a computed port, so a computed containerPort is not supported\\. Remove the port change\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Aspire.Hosting.Radius/Publishing/RadiusInfrastructureBuilder.cs","lineNumber":4984,"sourceCode":"            {\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 \" +\n                        $\"'{mapKey}'. Aspire service discovery already emitted this port ({expected.Port}) into \" +\n                        $\"consumer 'services__*' variables, so removing it would break cross-container calls. \" +\n                        $\"Remove the port change to keep service discovery consistent.\");\n                }\n\n                // Reject a non-literal port/protocol: service discovery is a fixed literal, so a\n                // callback that swaps in a Bicep expression could evaluate to a different value at\n                // deploy time, reintroducing exactly the mismatch this guard prevents. An\n                // expression-backed BicepValue<int> reports a default LiteralValue of 0 (not null),\n                // so a non-null Expression is the reliable \"non-literal\" signal, not the LiteralValue.\n                var portValueBicep = (IBicepValue)port.ContainerPort;\n                if (portValueBicep.Expression is not null || portValueBicep.LiteralValue is not int literalPort)\n                {\n                    throw new InvalidOperationException(\n                        $\"A ConfigureRadiusInfrastructure callback replaced port '{portName}' on container \" +\n                        $\"'{mapKey}' with a non-literal Bicep expression. Aspire service discovery already emitted \" +\n                        $\"the literal port {expected.Port} into consumer 'services__*' variables and cannot follow a \" +\n                        $\"computed port, so a computed containerPort is not supported. Remove the port change.\");\n                }\n\n                var protocolValueBicep = (IBicepValue)port.Protocol;\n                if (protocolValueBicep.Expression is not null || protocolValueBicep.LiteralValue is not string literalProtocol)\n                {\n                    throw new InvalidOperationException(\n                        $\"A ConfigureRadiusInfrastructure callback replaced the protocol of port '{portName}' on \" +\n                        $\"container '{mapKey}' with a non-literal Bicep expression. Aspire service discovery assumes \" +\n                        $\"the literal protocol '{expected.Protocol}', so a computed protocol is not supported. Remove \" +\n                        $\"the protocol change.\");\n                }\n\n                if (literalPort != expected.Port || !string.Equals(literalProtocol, expected.Protocol, StringComparison.Ordinal))\n                {","sourceCodeStart":4966,"sourceCodeEnd":5002,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Hosting.Radius/Publishing/RadiusInfrastructureBuilder.cs#L4966-L5002","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Remove the ConfigureRadiusInfrastructure callback line that changes the container port so the original literal port is kept","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","Keep any callback edits limited to properties Aspire does not cross-check, like image or env additions"],"exampleFix":"// before\ncallback: (builder) =>\n{\n    container.Ports[\"http\"].ContainerPort = BicepValue<int>.Create(myOutput); // computed\n}\n// after\ncallback: (builder) =>\n{\n    // leave the literal ContainerPort set on the resource untouched\n}","handlingStrategy":"validation","validationCode":"var v = (IBicepValue)port.ContainerPort;\nif (v.Expression is not null || v.LiteralValue is not int)\n    throw new InvalidOperationException($\"Port '{portName}' must keep its literal containerPort.\");","typeGuard":"static bool IsLiteralIntPort(IBicepValue v) => v.Expression is null && v.LiteralValue is int;","tryCatchPattern":null,"preventionTips":["Never assign Bicep expressions to ContainerPort inside ConfigureRadiusInfrastructure callbacks","Change ports on the Aspire resource definition, not in publish-time callbacks","Remember service discovery burns in literal ports into 'services__*' variables before callbacks run"],"tags":["radius","publish","bicep","port","service-discovery"],"backgroundTag":"unsupported-operation","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"}