microsoft/aspire · error · ArgumentOutOfRangeException

ResourceAnnotationMutationBehavior must be either Append or…

Error message

ResourceAnnotationMutationBehavior must be either Append or Replace.

What it means

DistributedApplicationResourceBuilder.WithAnnotation validates the ResourceAnnotationMutationBehavior argument and throws ArgumentOutOfRangeException when the value is neither Append nor Replace. This defensive check catches new enum members added without corresponding handling in the annotation mutation logic.

Solutions

  1. Pass only ResourceAnnotationMutationBehavior.Append or ResourceAnnotationMutationBehavior.Replace to WithAnnotation.
  2. Ensure all Aspire.Hosting packages are the same version so enum definitions and validation logic match.
  3. If behavior comes from configuration/dynamic code, validate/whitelist it to the two supported values before calling.

Example fix

// before
builder.WithAnnotation(new MyAnnotation(), (ResourceAnnotationMutationBehavior)3);
// after
builder.WithAnnotation(new MyAnnotation(), ResourceAnnotationMutationBehavior.Replace);
Defensive patterns

Strategy: validation

Validate before calling

if (behavior is not (ResourceAnnotationMutationBehavior.Append or ResourceAnnotationMutationBehavior.Replace))
    throw new ArgumentOutOfRangeException(nameof(behavior));

Type guard

bool IsValidMutationBehavior(ResourceAnnotationMutationBehavior b) => b is ResourceAnnotationMutationBehavior.Append or ResourceAnnotationMutationBehavior.Replace;

Try / catch

try { builder.WithAnnotation(annotation, behavior); } catch (ArgumentOutOfRangeException) { /* fall back to Append */ }

Prevention

When it happens

Trigger: Passing an invalid ResourceAnnotationMutationBehavior value to WithAnnotation<TAnnotation> — only possible with enum values outside Append/Replace (e.g. a cast of an arbitrary int, or code compiled against a newer enum version with extra members).

Common situations: Generic/reflective helper libraries that assign mutation behavior dynamically and pass raw enum values; binary-version mismatches where an extension compiled against a newer Aspire version passes an enum member the runtime's WithAnnotation doesn't accept; test code casting ints to the enum.

Related errors


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

Appendix: source

Thrown at src/Aspire.Hosting/DistributedApplicationResourceBuilder.cs:22

using Aspire.Hosting.ApplicationModel;

namespace Aspire.Hosting;

internal sealed class DistributedApplicationResourceBuilder<T>(IDistributedApplicationBuilder applicationBuilder, T resource) : IResourceBuilder<T> where T : IResource
{
    public T Resource { get; } = resource;
    public IDistributedApplicationBuilder ApplicationBuilder { get; } = applicationBuilder;

    /// <inheritdoc />
    public IResourceBuilder<T> WithAnnotation<TAnnotation>(TAnnotation annotation, ResourceAnnotationMutationBehavior behavior = ResourceAnnotationMutationBehavior.Append) where TAnnotation : IResourceAnnotation
    {
        ArgumentNullException.ThrowIfNull(annotation);

        // Some defensive code to protect against introducing a new enumeration value without first updating
        // this code to accommodate it.
        if (behavior != ResourceAnnotationMutationBehavior.Append && behavior != ResourceAnnotationMutationBehavior.Replace)
        {
            throw new ArgumentOutOfRangeException(nameof(behavior), behavior, "ResourceAnnotationMutationBehavior must be either Append or Replace.");
        }

        // If the behavior is AddReplace then there should never be more than one annotation present. The following call will result in an exception which
        // allows us to easily spot these bugs.
        if (behavior == ResourceAnnotationMutationBehavior.Replace && Resource.Annotations.OfType<TAnnotation>().SingleOrDefault() is { } existingAnnotation)
        {
            Resource.Annotations.Remove(existingAnnotation);
        }

        Resource.Annotations.Add(annotation);
        return this;
    }
}

View on GitHub (pinned to 25830f84bd)