App-vNext/Polly · error · ArgumentNullException

Value cannot be null. (Parameter 'action')

Error message

Value cannot be null. (Parameter 'action')

What it means

Thrown by AsyncRateLimitPolicy.ImplementationAsync<TResult> when the action delegate is null. This is the factory-less rate limit policy (no retryAfterFactory); it guards the delegate before delegating to AsyncRateLimitEngine, consistent with all Polly strategy implementations.

Source

Thrown at src/Polly/RateLimit/AsyncRateLimitPolicy.cs:24

/// </summary>
public class AsyncRateLimitPolicy : AsyncPolicy, IRateLimitPolicy
{
    private readonly IRateLimiter _rateLimiter;

    internal AsyncRateLimitPolicy(IRateLimiter rateLimiter) =>
        _rateLimiter = rateLimiter;

    /// <inheritdoc/>
    [DebuggerStepThrough]
    protected override Task<TResult> ImplementationAsync<TResult>(
        Func<Context, CancellationToken, Task<TResult>> action,
        Context context,
        CancellationToken cancellationToken,
        bool continueOnCapturedContext)
    {
        if (action is null)
        {
            throw new ArgumentNullException(nameof(action));
        }

        return AsyncRateLimitEngine.ImplementationAsync(
            _rateLimiter,
            null,
            action,
            context,
            continueOnCapturedContext,
            cancellationToken);
    }
}

/// <summary>
/// A rate-limit policy that can be applied to asynchronous delegates returning a value of type <typeparamref name="TResult"/>.
/// </summary>
/// <typeparam name="TResult">The type of the result.</typeparam>
public class AsyncRateLimitPolicy<TResult> : AsyncPolicy<TResult>, IRateLimitPolicy<TResult>
{

View on GitHub (pinned to d0e46bdb1e)

Solutions

  1. Always pass a non-null Func<Context, CancellationToken, Task<TResult>> to ExecuteAsync.
  2. If the delegate is optional in your API, branch around the call rather than passing null.
  3. Add a precondition null check at the call site.
  4. Enable nullable reference types to surface nullable delegates at compile time.

Example fix

// before
await rlPolicy.ExecuteAsync<int>(null, context, ct);

// after
await rlPolicy.ExecuteAsync<int>((ctx, ct) => Task.FromResult(42), context, ct);
Defensive patterns

Strategy: validation

Validate before calling

if (action is null) throw new ArgumentNullException(nameof(action));
await rlPolicy.ExecuteAsync(action ?? throw new ArgumentNullException(nameof(action)), context, ct);

Type guard

static bool IsAsyncFunc<T>(Delegate d) => d is Func<Context, CancellationToken, Task<T>>;

Prevention

When it happens

Trigger: Invoking ImplementationAsync with a null action on an AsyncRateLimitPolicy instance. Normally reached via internal Policy.ExecuteAsync plumbing when a null delegate reaches the strategy, or via direct subclass/reflection use.

Common situations: A wrapper that conditionally passes a delegate and forwards null; a refactor that introduced a nullable action parameter; reflection-driven invocation in tests.

Related errors


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