{"record":{"id":"d4c6a8aad556c5db","repo":"App-vNext/Polly","slug":"value-must-be-greater-than-or-equal-to-zero-d4c6a8","errorCode":null,"errorMessage":"Value must be greater than or equal to zero.","messagePattern":"Value must be greater than or equal to zero\\.","errorType":"validation","errorClass":"ArgumentOutOfRangeException","httpStatus":null,"severity":"error","filePath":"src/Polly/Retry/AsyncRetryTResultSyntax.cs","lineNumber":161,"sourceCode":"#pragma warning restore 1998\n    }\n\n    /// <summary>\n    /// Builds an <see cref=\"AsyncRetryPolicy{TResult}\"/> that will retry <paramref name=\"retryCount\"/> times\n    /// calling <paramref name=\"onRetryAsync\"/> on each retry with the handled exception or result, retry count and context data.\n    /// </summary>\n    /// <typeparam name=\"TResult\">The type of the result.</typeparam>\n    /// <param name=\"policyBuilder\">The policy builder.</param>\n    /// <param name=\"retryCount\">The retry count.</param>\n    /// <param name=\"onRetryAsync\">The action to call asynchronously on each retry.</param>\n    /// <returns>The policy instance.</returns>\n    /// <exception cref=\"ArgumentOutOfRangeException\">retryCount;Value must be greater than zero.</exception>\n    /// <exception cref=\"ArgumentNullException\">Thrown when <paramref name=\"onRetryAsync\"/> is <see langword=\"null\"/>.</exception>\n    public static AsyncRetryPolicy<TResult> RetryAsync<TResult>(this PolicyBuilder<TResult> policyBuilder, int retryCount, Func<DelegateResult<TResult>, int, Context, Task> onRetryAsync)\n    {\n        if (retryCount < 0)\n        {\n            throw new ArgumentOutOfRangeException(nameof(retryCount), \"Value must be greater than or equal to zero.\");\n        }\n\n        if (onRetryAsync == null)\n        {\n            throw new ArgumentNullException(nameof(onRetryAsync));\n        }\n\n        return new AsyncRetryPolicy<TResult>(\n            policyBuilder,\n            (outcome, _, i, ctx) => onRetryAsync(outcome, i, ctx),\n            retryCount);\n    }\n\n    /// <summary>\n    ///     Builds an <see cref=\"AsyncRetryPolicy{TResult}\" /> that will retry indefinitely until the action succeeds.\n    /// </summary>\n    /// <typeparam name=\"TResult\">The type of the result.</typeparam>\n    /// <param name=\"policyBuilder\">The policy builder.</param>","sourceCodeStart":143,"sourceCodeEnd":179,"githubUrl":"https://github.com/App-vNext/Polly/blob/d0e46bdb1ee11ea50d0e4b6846d2633d6bc09bac/src/Polly/Retry/AsyncRetryTResultSyntax.cs#L143-L179","documentation":"Thrown by RetryAsync<TResult> when retryCount is negative (the guard is retryCount < 0). The message says 'Value must be greater than or equal to zero' — note the XML doc incorrectly says 'greater than zero', but zero retries (i.e. no retries) is actually permitted. This overload is the terminal context-aware async variant; it validates retryCount before onRetryAsync. A negative count has no semantic meaning for a retry policy.","triggerScenarios":"Passing a literal negative number, computing retryCount from config that defaulted to -1, or subtracting from a count and going below zero (e.g. retryCount - 1 when retryCount is 0).","commonSituations":"Config value missing and parsed to a sentinel like -1, arithmetic underflow in a derived count, or an environment-specific override that was never validated.","solutions":["Clamp the value before the call: Math.Max(0, retryCount).","Validate config at startup and fail with a clear message if retryCount < 0.","Note that 0 is valid (policy never retries); only negative throws."],"exampleFix":"// before\nPolicy.HandleResult<T>(IsHandled).RetryAsync(retryCountFromConfig, onRetryAsync);\n// after\nvar retryCount = Math.Max(0, retryCountFromConfig);\nPolicy.HandleResult<T>(IsHandled).RetryAsync(retryCount, onRetryAsync);","handlingStrategy":"validation","validationCode":"if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), \"retryCount must be >= 0\");\n// safe retryCount is Math.Max(0, retryCount) if a default of 0 is acceptable","typeGuard":"static bool IsValidRetryCount(int count) => count >= 0;","tryCatchPattern":"try { Policy.HandleResult<T>(IsHandled).RetryAsync(retryCount, onRetryAsync); }\ncatch (ArgumentOutOfRangeException ex) when (ex.ParamName == nameof(retryCount))\n{ retryCount = 0; /* rebuild with a valid count */ }","preventionTips":["Validate config-sourced retry counts at startup and reject negative sentinels early.","Clamp derived counts with Math.Max(0, value) when a 0-retry fallback is acceptable.","Add a contract test that the configured retry count is non-negative across environments."],"tags":["polly","retry","argument-out-of-range","config","result-typed","legacy"],"backgroundTag":null,"analyzedSha":"d0e46bdb1ee11ea50d0e4b6846d2633d6bc09bac","analyzedAt":"2026-08-13T16:36:01.959Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}