App-vNext/Polly · error · ArgumentNullException

Value cannot be null.

Error message

Value cannot be null.

What it means

Thrown by AsyncRetryPolicy.ImplementationAsync<TResult> (the generic, exception-predicate-based legacy retry policy) when the action delegate is null. The retry engine needs a delegate to invoke on each attempt.

Source

Thrown at src/Polly/Retry/AsyncRetryPolicy.cs:38

    {
        _permittedRetryCount = permittedRetryCount;
        _sleepDurationsEnumerable = sleepDurationsEnumerable;
        _sleepDurationProvider = sleepDurationProvider;
        _onRetryAsync = onRetryAsync;
    }

    /// <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));
        }

        var sleepDurationProvider = _sleepDurationProvider != null
            ? (retryCount, outcome, ctx) => _sleepDurationProvider(retryCount, outcome.Exception, ctx)
            : (Func<int, DelegateResult<TResult>, Context, TimeSpan>)null;

        return AsyncRetryEngine.ImplementationAsync(
            action,
            context,
            ExceptionPredicates,
            ResultPredicates<TResult>.None,
            (outcome, timespan, retryCount, ctx) => _onRetryAsync(outcome.Exception, timespan, retryCount, ctx),
            cancellationToken,
            _permittedRetryCount,
            _sleepDurationsEnumerable,
            sleepDurationProvider,
            continueOnCapturedContext);
    }

View on GitHub (pinned to d0e46bdb1e)

Solutions

  1. Pass a non-null Func<Context, CancellationToken, Task<TResult>>.
  2. Verify the action variable is assigned before ExecuteAsync.
  3. Use a no-op delegate returning default(TResult) if a placeholder is needed.

Example fix

// before
await policy.ExecuteAsync(null, context, ct);
// after
await policy.ExecuteAsync(_ => Task.FromResult(result), context, ct);
Defensive patterns

Strategy: validation

Validate before calling

if (action is null) throw new ArgumentException("action must be set before executing the retry policy.");
await policy.ExecuteAsync(action, context, cancellationToken);

Type guard

static bool IsActionSet<TResult>(Func<Context, CancellationToken, Task<TResult>> action) => action is not null;

Prevention

When it happens

Trigger: Calling ExecuteAsync with a null action on an AsyncRetryPolicy built via the legacy RetryAsync exception-based syntax. The protected ImplementationAsync is reached through the base ExecuteAsync path.

Common situations: Passing a method-group that resolved to null; an action selected conditionally that stayed null; refactor that dropped the delegate argument.

Related errors


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