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

This generic Retry<TResult> overload (RetryTResultSyntax.cs:54) throws ArgumentOutOfRangeException synchronously when retryCount is negative. The guard `retryCount < 0` enforces a valid attempt budget for the result-typed retry policy at construction time.

Source

Thrown at src/Polly/Retry/RetryTResultSyntax.cs:54

    public static RetryPolicy<TResult> Retry<TResult>(this PolicyBuilder<TResult> policyBuilder, Action<DelegateResult<TResult>, int> onRetry) =>
        policyBuilder.Retry(1, onRetry);

    /// <summary>
    /// Builds a <see cref="Policy{TResult}"/> that will retry <paramref name="retryCount"/> times
    /// calling <paramref name="onRetry"/> on each retry with the handled exception or result and retry count.
    /// </summary>
    /// <typeparam name="TResult">The type of the result.</typeparam>
    /// <param name="policyBuilder">The policy builder.</param>
    /// <param name="retryCount">The retry count.</param>
    /// <param name="onRetry">The action to call on each retry.</param>
    /// <returns>The policy instance.</returns>
    /// <exception cref="ArgumentOutOfRangeException">retryCount;Value must be greater than or equal to zero.</exception>
    /// <exception cref="ArgumentNullException">Thrown when <paramref name="onRetry"/> is <see langword="null"/>.</exception>
    public static RetryPolicy<TResult> Retry<TResult>(this PolicyBuilder<TResult> policyBuilder, int retryCount, Action<DelegateResult<TResult>, int> onRetry)
    {
        if (retryCount < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(retryCount), "Value must be greater than or equal to zero.");
        }

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

        return policyBuilder.Retry(retryCount, (outcome, i, _) => onRetry(outcome, i));
    }

    /// <summary>
    /// Builds a <see cref="Policy{TResult}"/> that will retry once
    /// calling <paramref name="onRetry"/> on retry with the handled exception or result, retry count and context data.
    /// </summary>
    /// <typeparam name="TResult">The type of the result.</typeparam>
    /// <param name="policyBuilder">The policy builder.</param>
    /// <param name="onRetry">The action to call on each retry.</param>
    /// <returns>The policy instance.</returns>

View on GitHub (pinned to d0e46bdb1e)

Solutions

  1. Pass retryCount >= 0 (0 means no retries).
  2. Clamp config-sourced values with Math.Max(0, value).
  3. Trace where the negative value originates.

Example fix

// before
var policy = Policy.HandleResult<int>(r => r < 0).Retry(-1, onRetry);
// after
var retryCount = Math.Max(0, configuredRetries);
var policy = Policy.HandleResult<int>(r => r < 0).Retry(retryCount, onRetry);
Defensive patterns

Strategy: validation

Validate before calling

if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount));
var clamped = Math.Max(0, retryCount);
var policy = Policy.HandleResult<int>(r => r < 0).Retry(clamped, onRetry);

Prevention

When it happens

Trigger: Calling Policy.HandleResult<T>(...).Retry(retryCount, onRetry) where retryCount is negative, with onRetry typed Action<DelegateResult<TResult>, int>.

Common situations: Computed retry count that underflows (e.g. configured - 1 where configured is 0); a config value defaulting to -1; sentinel misuse.

Related errors


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