microsoft/aspire · error · InvalidOperationException

ASPIRERADIUS056

ASPIRERADIUS056

Error message

Two Radius constructs emit the same Bicep identifier '{identifier}' ({existing} and {description}). Bicep symbolic names share a single flat namespace, so every emitted resource and parameter must have a distinct identifier. Rename the conflicting resource — note that resource names are sanitized to Bicep identifiers (e.g. 'my-x' and 'my.x' both become 'my_x'), and that the publisher reserves the identifiers 'app', 'app_legacy', and 'recipepack' for its synthesized constructs. Diagnostic: ASPIRERADIUS056.

What it means

The Radius publisher emits resources and parameters into a single flat Bicep namespace, and ValidateUniqueIdentifiers (called by CompileBicep) throws InvalidOperationException with diagnostic ASPIRERADIUS056 when two constructs sanitize to the same Bicep identifier. Note that names are sanitized (my-x and my.x both become my_x) and 'app', 'app_legacy', and 'recipepack' are reserved by the publisher.

Solutions

  1. Rename one of the conflicting resources so the sanitized identifiers differ (e.g. 'my-x' vs 'my-x2', or use different word separators).
  2. Rename any resource that uses the reserved identifiers 'app', 'app_legacy', or 'recipepack'.
  3. Check the error message's '{existing} and {description}' text to identify exactly which two constructs collide and adjust the one you control.
  4. Verify each emitted resource/parameter name is unique after sanitization by mentally replacing '-', '.', and other non-identifier characters with '_'.

Example fix

// before
var a = builder.AddContainer("my-x", "image");
var b = builder.AddContainer("my.x", "image"); // both sanitize to my_x
// after
var a = builder.AddContainer("my-x", "image");
var b = builder.AddContainer("my-x2", "image");
Defensive patterns

Strategy: validation

Validate before calling

static string SanitizeBicepIdentifier(string name) =>
    new string(name.Select(c => char.IsLetterOrDigit(c) || c == '_' ? c : '_').ToArray());
var reserved = new[] { "app", "app_legacy", "recipepack" };
var ids = resources.Select(r => SanitizeBicepIdentifier(r.Name)).ToList();
if (ids.Distinct().Count() != ids.Count || ids.Any(reserved.Contains))
    throw new InvalidOperationException("Bicep identifier collision or reserved name detected");

Try / catch

try { PublishAsRadiusApp(...); }
catch (InvalidOperationException ex) when (ex.Message.Contains("ASPIRERADIUS056")) { logger.LogError(ex, "Duplicate Bicep identifiers in Radius publish"); throw; }

Prevention

When it happens

Trigger: Two Radius resources whose names differ only by characters removed during sanitization (e.g. 'my-x' and 'my.x' → 'my_x'); a user resource named 'app', 'app_legacy', or 'recipepack' colliding with the publisher's synthesized constructs; a resource named identically to an emitted parameter.

Common situations: Renaming resources from dashes to dots for different environments while both remain in the model; creating a container literally named 'app'; renaming one of two colliding resources but leaving the other unchanged.

Related errors


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

Appendix: source

Thrown at src/Aspire.Hosting.Radius/Publishing/BicepPostProcessor.cs:313

        {
            BicepDictionary<object> nestedObject => nestedObject,
            BicepList<object> nestedArray => nestedArray,
            var scalar => new BicepValue<object>(scalar)
        };
    }

    private static void ValidateUniqueIdentifiers(RadiusInfrastructureOptions options)
    {
        // Maps each claimed Bicep identifier to a human-readable description of the construct that
        // first claimed it, so the diagnostic can name both sides of a collision. Ordinal because
        // Bicep identifiers are case-sensitive.
        var seen = new Dictionary<string, string>(StringComparer.Ordinal);

        void Register(string identifier, string description)
        {
            if (seen.TryGetValue(identifier, out var existing))
            {
                throw new InvalidOperationException(
                    $"Two Radius constructs emit the same Bicep identifier '{identifier}' ({existing} and {description}). " +
                    "Bicep symbolic names share a single flat namespace, so every emitted resource and parameter must " +
                    "have a distinct identifier. Rename the conflicting resource — note that resource names are sanitized " +
                    "to Bicep identifiers (e.g. 'my-x' and 'my.x' both become 'my_x'), and that the publisher reserves the " +
                    "identifiers 'app', 'app_legacy', and 'recipepack' for its synthesized constructs. Diagnostic: ASPIRERADIUS056.");
            }

            seen[identifier] = description;
        }

        // Enumerate every collection added to the flat namespace in CompileBicep, in the same order.
        foreach (var pack in options.RecipePacks)
        {
            Register(pack.BicepIdentifier, "a recipe pack");
        }

        foreach (var environment in options.Environments)
        {

View on GitHub (pinned to 25830f84bd)