App-vNext/Polly · error · ArgumentOutOfRangeException
The retryAfter parameter must be a TimeSpan greater than or
Error message
The retryAfter parameter must be a TimeSpan greater than or equal to TimeSpan.Zero.
What it means
Thrown by RateLimitRejectedException.SetRetryAfter when retryAfter is less than TimeSpan.Zero. The exception's RetryAfter property must be a valid non-negative retry delay because callers use it to schedule the next attempt; a negative value would be meaningless.
Source
Thrown at src/Polly/RateLimit/RateLimitRejectedException.cs:92
: base(message) => SetRetryAfter(retryAfter);
/// <summary>
/// Initializes a new instance of the <see cref="RateLimitRejectedException"/> class.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="retryAfter">The timespan after which the operation may be retried.</param>
/// <param name="innerException">The inner exception.</param>
public RateLimitRejectedException(TimeSpan retryAfter, string message, Exception innerException)
: base(message, innerException) => SetRetryAfter(retryAfter);
private static string DefaultMessage(TimeSpan retryAfter) =>
$"The operation has been rate-limited and should be retried after {retryAfter}";
private void SetRetryAfter(TimeSpan retryAfter)
{
if (retryAfter < TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException(nameof(retryAfter), retryAfter, $"The {nameof(retryAfter)} parameter must be a TimeSpan greater than or equal to TimeSpan.Zero.");
}
RetryAfter = retryAfter;
}
#if NETSTANDARD2_0
/// <summary>
/// Initializes a new instance of the <see cref="RateLimitRejectedException"/> class.
/// </summary>
/// <param name="info">The information.</param>
/// <param name="context">The context.</param>
protected RateLimitRejectedException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
#endif
}
View on GitHub (pinned to d0e46bdb1e)
Solutions
- Clamp the computed retry delay with TimeSpan.Max(value, TimeSpan.Zero) before constructing the exception.
- Validate the source (e.g. Retry-After header) is non-negative before parsing to TimeSpan.
- If the delay is genuinely unknown, pass TimeSpan.Zero rather than a negative value.
Example fix
// before var retryAfter = serverReset - DateTimeOffset.UtcNow; throw new RateLimitRejectedException(retryAfter, msg, inner); // after var retryAfter = TimeSpan.Max(serverReset - DateTimeOffset.UtcNow, TimeSpan.Zero); throw new RateLimitRejectedException(retryAfter, msg, inner);
Defensive patterns
Strategy: validation
Validate before calling
var retryAfter = serverReset - DateTimeOffset.UtcNow; if (retryAfter < TimeSpan.Zero) retryAfter = TimeSpan.Zero; throw new RateLimitRejectedException(retryAfter, message, inner);
Prevention
- Always clamp TimeSpan deltas with TimeSpan.Max(value, TimeSpan.Zero) before constructing rate-limit exceptions.
- Parse and validate Retry-After header values as non-negative before converting to TimeSpan.
- Account for clock skew when computing server-side reset times.
When it happens
Trigger: Constructing new RateLimitRejectedException(retryAfter, ...) (any overload chaining SetRetryAfter) with a TimeSpan that is negative. Typically happens when a custom retryAfterFactory or downstream code computes a delay that underflows.
Common situations: Clock skew or a subtracted offset producing a negative TimeSpan; a retry-after header parsed as a negative value; arithmetic on timestamps (e.g. serverTime - now) when now is ahead.
Related errors
- perTimeSpan must be a positive timespan.
- The number of executions per timespan must be positive.
- perTimeSpan must be a positive timespan.
- The number of executions per timespan must be positive.
- numberOfExecutions per timespan must be an integer greater t
AI-assisted analysis of App-vNext/Polly@d0e46bdb1e (2026-08-13).
Data as JSON: /api/errors/1e70b85bdfc03492.
Report an issue: GitHub.