App-vNext/Polly · error · ArgumentOutOfRangeException

Value must be greater than or equal to zero.

Error message

Value must be greater than or equal to zero.

What it means

Thrown by AsyncRetrySyntax.RetryAsync(this PolicyBuilder, retryCount, Func<Exception,int,Context,Task> onRetryAsync) when retryCount is less than zero. A negative retry count is nonsensical; zero means no retries (fail fast) and is the valid floor.

Source

Thrown at src/Polly/Retry/AsyncRetrySyntax.cs:151

            onRetryAsync: async (outcome, i, ctx) => onRetry(outcome, i, ctx));
#pragma warning restore 1998
    }

    /// <summary>
    /// Builds an <see cref="AsyncRetryPolicy"/> that will retry <paramref name="retryCount"/> times
    /// calling <paramref name="onRetryAsync"/> on each retry with the raised exception, retry count and context data.
    /// </summary>
    /// <param name="policyBuilder">The policy builder.</param>
    /// <param name="retryCount">The retry count.</param>
    /// <param name="onRetryAsync">The action to call asynchronously on each retry.</param>
    /// <returns>The policy instance.</returns>
    /// <exception cref="ArgumentOutOfRangeException">retryCount;Value must be greater than zero.</exception>
    /// <exception cref="ArgumentNullException">Thrown when <paramref name="onRetryAsync"/> is <see langword="null"/>.</exception>
    public static AsyncRetryPolicy RetryAsync(this PolicyBuilder policyBuilder, int retryCount, Func<Exception, int, Context, Task> onRetryAsync)
    {
        if (retryCount < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero.");
        }

        if (onRetryAsync == null)
        {
            throw new ArgumentNullException(nameof(onRetryAsync));
        }

        return new AsyncRetryPolicy(
            policyBuilder,
            (outcome, _, i, ctx) => onRetryAsync(outcome, i, ctx),
            retryCount);
    }

    /// <summary>
    ///     Builds an <see cref="AsyncRetryPolicy" /> that will retry indefinitely until the action succeeds.
    /// </summary>
    /// <param name="policyBuilder">The policy builder.</param>
    /// <returns>The policy instance.</returns>

View on GitHub (pinned to d0e46bdb1e)

Solutions

  1. Set retryCount to 0 or a positive integer (0 = no retries).
  2. Clamp the config value with Math.Max(0, configuredValue) at load time.
  3. Use a distinct sentinel and map it to a default retry count rather than passing it through.

Example fix

// before
var policy = Policy.Handle<HttpRequestException>().RetryAsync(-1, onRetryAsync);
// after
var policy = Policy.Handle<HttpRequestException>().RetryAsync(3, onRetryAsync);
Defensive patterns

Strategy: validation

Validate before calling

var retryCount = Math.Max(0, config.GetValue("Retry:Count", 3));
var policy = builder.RetryAsync(retryCount, onRetryAsync);

Prevention

When it happens

Trigger: Calling .RetryAsync(retryCount: -1, ...) on the context-aware async-callback overload; any negative integer for retryCount.

Common situations: Config-driven retry count defaulting to -1 (sentinel for "unset"); arithmetic that subtracts from retry count; env var parsed as a negative number.

Related errors


AI-assisted analysis of App-vNext/Polly@d0e46bdb1e (2026-08-13). Data as JSON: /api/errors/9e25fdd356279396. Report an issue: GitHub.