microsoft/aspire · error · InvalidOperationException
ASPIRERADIUS028
ASPIRERADIUS028
Error message
Recipe parameters bound to Aspire parameters '{existingName}' and '{parameter.Name}' both map to the Bicep identifier '{identifier}'. Rename one of the parameters so they produce distinct Bicep identifiers. Diagnostic: ASPIRERADIUS028. What it means
Two distinct Aspire parameter names can sanitize to the same Bicep identifier (e.g. 'my-key' and 'my.key' both become 'my_key'), which would emit duplicate 'param my_key' declarations and invalid Bicep. Aspire detects the collision in _recipeParameterIdentifiers and throws, tagging it with diagnostic code ASPIRERADIUS028.
Solutions
- Rename one of the conflicting Aspire parameters so the sanitized identifiers differ
- Use a value-parameter instead of a named parameter for one of the two bindings
- Check all WithParameter names feeding Radius recipe parameters for collisions before publishing
Example fix
// before
builder.AddParameter("my-key"); builder.AddParameter("my.key"); // both -> my_key
// after
builder.AddParameter("my-key"); builder.AddParameter("my-key-2"); Defensive patterns
Strategy: validation
Validate before calling
static string Sanitize(string name) => new string(name.Select(c => char.IsLetterOrDigit(c) ? c : '_').ToArray()).ToLowerInvariant();
var dupes = paramNames.GroupBy(Sanitize).Where(g => g.Count() > 1).ToList();
if (dupes.Count > 0) throw new Exception($"Parameters collide on Bicep identifier: {string.Join(", ", dupes.Select(d => string.Join("/", d)))}"); Prevention
- Sanitize parameter names (lowercase, single-underscore separators) before choosing them
- Grep the AppHost for parameters differing only by '-', '.', '_'
- Add a startup check that all WithParameter names sanitize to unique identifiers
When it happens
Trigger: Defining two recipe parameters bound to Aspire parameters whose names normalize to the same Bicep identifier (differing only by characters sanitized to '_', '-', '.'), e.g. 'my-key' and 'my.key' or 'MyKey'.
Common situations: Kebab-case vs dot-notation parameter names used across a solution converging to the same identifier when the Radius environment publishes; renaming one parameter into collision with an existing one.
Understand the failure class
Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.
Related errors
- ASPIRERADIUS048
- ASPIRERADIUS056
- 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/f6c5b9dd92aa6788.
Report an issue: GitHub.
Appendix: source
Thrown at src/Aspire.Hosting.Radius/Publishing/RadiusInfrastructureBuilder.cs:5441
// GetOrAddEnvParameter already performs in the opposite direction, so the two
// allocators agree regardless of which one runs first. Deliberately not cached in
// _recipeParameters: the env allocator emits it through options.Parameters, and the
// deploy binding was already recorded in _deployParametersByIdentifier, which
// RecordDeployParameters merges with the recipe bindings.
if (_envParametersByName.TryGetValue(parameter.Name, out var envParameter))
{
return envParameter;
}
var identifier = BicepPostProcessor.SanitizeIdentifier(parameter.Name);
// Two distinct parameter names can sanitize to the same Bicep identifier (e.g.
// "my-key" and "my.key" both become "my_key"). Emitting two `param my_key`
// declarations produces invalid Bicep, so fail with an actionable diagnostic
// (ASPIRERADIUS028) instead.
if (_recipeParameterIdentifiers.TryGetValue(identifier, out var existingName))
{
throw new InvalidOperationException(
$"Recipe parameters bound to Aspire parameters '{existingName}' and '{parameter.Name}' both " +
$"map to the Bicep identifier '{identifier}'. Rename one of the parameters so they produce " +
"distinct Bicep identifiers. Diagnostic: ASPIRERADIUS028.");
}
provisioningParameter = new ProvisioningParameter(identifier, typeof(string))
{
IsSecure = parameter.Secret,
};
_recipeParameters[parameter.Name] = provisioningParameter;
_recipeParameterIdentifiers[identifier] = parameter.Name;
// Remember the originating ParameterResource keyed by the Bicep identifier so the
// deploy step can pass `--parameters <identifier>=<value>` for this valueless param.
_recipeParameterBindings[identifier] = parameter;
}
return provisioningParameter;
}View on GitHub (pinned to 25830f84bd)