App-vNext/Polly · error · InvalidOperationException

Cannot add any more resilience strategies to the builder aft

Error message

Cannot add any more resilience strategies to the builder after it has been used to build a pipeline once.

What it means

Once ResiliencePipelineBuilderBase.BuildPipelineComponent() runs, the internal _used flag is set and subsequent AddPipelineComponent calls throw InvalidOperationException. Builders are single-use: the built ResiliencePipeline captures the strategy chain, so mutating the builder afterward would not affect the already-built pipeline and is forbidden to keep semantics clear.

Source

Thrown at src/Polly.Core/ResiliencePipelineBuilderBase.cs:112

    /// </summary>
    /// <value>The default value is a validation function that uses data annotations for validation.</value>
    /// <remarks>
    /// The validator should throw <see cref="ValidationException"/> when the validated instance is invalid.
    /// </remarks>
    /// <exception cref="ArgumentNullException">Thrown when the attempting to assign <see langword="null"/> to this property.</exception>
    internal Action<ResilienceValidationContext> Validator { get; private protected set; } = ValidationHelper.ValidateObject;

    [RequiresUnreferencedCode(Constants.OptionsValidation)]
    internal void AddPipelineComponent(Func<StrategyBuilderContext, PipelineComponent> factory, ResilienceStrategyOptions options)
    {
        Guard.NotNull(factory);
        Guard.NotNull(options);

        Validator(new ResilienceValidationContext(options, $"The '{TypeNameFormatter.Format(options.GetType())}' are invalid."));

        if (_used)
        {
            throw new InvalidOperationException("Cannot add any more resilience strategies to the builder after it has been used to build a pipeline once.");
        }

        _entries.Add(new(factory, options));
    }

    internal PipelineComponent BuildPipelineComponent()
    {
        Validator(new(this, $"The '{nameof(ResiliencePipelineBuilder)}' configuration is invalid."));

        _used = true;

        var components = _entries.ConvertAll(CreateComponent);

        if (components.Count == 0)
        {
            return PipelineComponent.Empty;
        }

View on GitHub (pinned to d0e46bdb1e)

Solutions

  1. Configure all strategies before calling Build(); build exactly once per builder instance.
  2. If you need a variant pipeline, create a new builder (optionally via the copy constructor) instead of mutating a used one.
  3. Cache the built ResiliencePipeline, not the builder, when you need to reuse the result.

Example fix

// before
var builder = new ResiliencePipelineBuilder().AddRetry(new());
var pipeline = builder.Build();
builder.AddCircuitBreaker(new()); // throws

// after
var pipeline = new ResiliencePipelineBuilder()
    .AddRetry(new())
    .AddCircuitBreaker(new())
    .Build(); // configure fully, build once
Defensive patterns

Strategy: validation

Validate before calling

// Track whether you have built:
ResiliencePipeline? built = null;
void AddMore() {
    if (built != null) throw new InvalidOperationException("Builder already used; create a new one.");
    builder.AddStrategy(...);
}

Prevention

When it happens

Trigger: Calling .AddRetry(...)/.AddCircuitBreaker(...)/etc. on a builder after .Build() has already been called on it. Also reusing a builder that DI helper pipelines (AddResiliencePipeline) internally built.

Common situations: Storing a builder in a static/DI singleton, building it, then trying to append strategies later; fluent chains split across methods where one branch calls Build() early; passing the same builder to two callers who both configure and build.

Related errors


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