microsoft/aspire · error · DistributedApplicationException

Unexpected wait behavior

Error message

Unexpected wait behavior: {waitBehavior}

What it means

Internal invariant guard in ShouldYieldHealthyWait: the waitBehavior value did not match WaitOnResourceUnavailable or StopOnResourceUnavailable, so the switch fell to the default arm. WaitBehavior is an enum and this indicates an unknown/invalid enum value was supplied.

Solutions

  1. Only pass explicitly defined members: WaitBehavior.WaitOnResourceUnavailable or WaitBehavior.StopOnResourceUnavailable.
  2. Audit custom code or extensions that construct WaitAnnotation and remove int casts or default(WaitBehavior) usage.
  3. Align package versions across all Aspire projects so the enum definition matches the hosting library.

Example fix

// before
var annotation = new WaitAnnotation(dep, WaitType.WaitUntilHealthy) { WaitBehavior = (WaitBehavior)3 };

// after
var annotation = new WaitAnnotation(dep, WaitType.WaitUntilHealthy) { WaitBehavior = WaitBehavior.StopOnResourceUnavailable };
Defensive patterns

Strategy: validation

Validate before calling

bool IsValid(WaitBehavior b) => b is WaitBehavior.WaitOnResourceUnavailable or WaitBehavior.StopOnResourceUnavailable;
if (!IsValid(annotation.WaitBehavior)) throw new ArgumentException($"Unsupported WaitBehavior: {annotation.WaitBehavior}");

Type guard

static bool IsDefinedWaitBehavior(WaitBehavior value) =>
    Enum.IsDefined(typeof(WaitBehavior), value);

Try / catch

try
{
    // wait annotation processing
}
catch (DistributedApplicationException ex) when (ex.Message.Contains("Unexpected wait behavior"))
{
    logger.LogError(ex, "Invalid WaitBehavior enum value supplied.");
}

Prevention

When it happens

Trigger: Passing an out-of-range or undefined WaitBehavior value (e.g. a cast of an arbitrary int or a default value that is not a defined member) into a wait annotation / WaitFor API that evaluates ShouldYieldHealthyWait.

Common situations: Custom resource extensions or copy-pasted wait annotation code passing an uninitialized enum (default 0 if not a defined member); library version drift where an older caller passes a WaitBehavior value removed or renamed in a newer Aspire version.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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

Appendix: source

Thrown at src/Aspire.Hosting/ApplicationModel/ResourceNotificationService.cs:295

        // Observe the result of the resource ready event task
        await resourceEvent.Snapshot.ResourceReadyEvent!.EventTask.WaitAsync(cancellationToken).ConfigureAwait(false);

        _logger.LogDebug("Finished waiting for resource '{ResourceName}'.", resourceName);

        return resourceEvent;
    }

    internal static bool ShouldYieldHealthyWait(WaitBehavior waitBehavior, CustomResourceSnapshot snapshot) =>
        waitBehavior switch
        {
            WaitBehavior.WaitOnResourceUnavailable => snapshot.HealthStatus == HealthStatus.Healthy,
            WaitBehavior.StopOnResourceUnavailable => snapshot.HealthStatus == HealthStatus.Healthy ||
                                                  snapshot.State?.Text == KnownResourceStates.Finished ||
                                                  snapshot.State?.Text == KnownResourceStates.Exited ||
                                                  snapshot.State?.Text == KnownResourceStates.FailedToStart ||
                                                  snapshot.State?.Text == KnownResourceStates.RuntimeUnhealthy,
            _ => throw new DistributedApplicationException($"Unexpected wait behavior: {waitBehavior}")
        };

    private async Task WaitUntilCompletionAsync(IResource resource, IResource dependency, int exitCode, CancellationToken cancellationToken, Func<string, Task>? onDependencyReady = null)
    {
        using var activity = ProfilingTelemetry.StartResourceWaitForDependency(Configuration, resource, dependency, WaitType.WaitForCompletion, waitBehavior: null);
        activity.SetResourceWaitExpectedExitCode(exitCode);

        var names = dependency.GetResolvedResourceNames();
        var tasks = new Task[names.Length];

        var resourceLogger = _resourceLoggerService.GetLogger(resource);
        resourceLogger.LogInformation("Waiting for resource '{ResourceName}' to complete.", dependency.Name);

        for (var i = 0; i < names.Length; i++)
        {
            var displayName = names.Length > 1 ? names[i] : dependency.Name;
            tasks[i] = Core(displayName, names[i]);
        }

View on GitHub (pinned to 25830f84bd)