microsoft/aspire · error · InvalidOperationException

Resource update buffering was not enabled for this watcher.

Error message

Resource update buffering was not enabled for this watcher.

What it means

ResourceSnapshotWatcher can be constructed without update buffering; WatchResourceSnapshotBatchesAsync requires the internal buffered update channel (_updateSignal) to exist. If buffering was never enabled, the watcher throws InvalidOperationException because it has no way to stream snapshot updates. This is a programming/configuration error, not a runtime fault.

Solutions

  1. Construct ResourceSnapshotWatcher with update buffering enabled (the option that allocates the update signal channel).
  2. Create a dedicated watcher instance for streaming updates instead of reusing a polling-only watcher.
  3. Fall back to polling GetResources/CaptureAllResources if streaming is not required.

Example fix

// before
var watcher = new ResourceSnapshotWatcher(connection); // buffering not enabled
await watcher.WatchResourceSnapshotBatchesAsync(0, ct);
// after
var watcher = new ResourceSnapshotWatcher(connection, new ResourceSnapshotWatcherOptions { EnableUpdateBuffering = true });
await watcher.WatchResourceSnapshotBatchesAsync(0, ct);
Defensive patterns

Strategy: validation

Validate before calling

// before streaming, ensure buffering was enabled at construction
if (!watcherOptions.EnableUpdateBuffering) throw new NotSupportedException("Create the watcher with update buffering enabled to stream updates.");

Try / catch

try { await foreach (var b in watcher.WatchResourceSnapshotBatchesAsync(0, ct)) { } }
catch (InvalidOperationException ex) when (ex.Message.Contains("buffering")) { /* fall back to polling GetResources */ }

Prevention

When it happens

Trigger: Calling WatchResourceSnapshotBatchesAsync on a ResourceSnapshotWatcher instance that was created with update buffering disabled (no capacity/options enabling the update signal buffer).

Common situations: Reusing a watcher built for one-shot polling (CaptureAllResources/GetResources) and then trying to stream updates from it; constructing the watcher with default options that don't enable buffering.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16). Data as JSON: /api/errors/07540decb1c4ac4a. Report an issue: GitHub.

Appendix: source

Thrown at src/Aspire.Cli/Backchannel/ResourceSnapshotWatcher.cs:214

                    retainedVersion);
                continue;
            }

            _updateSignal?.Writer.TryWrite(true);
        }
    }

    /// <summary>
    /// Streams updates from the same subscription that maintains the current resource collection.
    /// Callers should first capture the initial state with <see cref="CaptureAllResources"/>.
    /// The update stream can be enumerated only once over the lifetime of the watcher.
    /// </summary>
    public async IAsyncEnumerable<ResourceSnapshotUpdateBatch> WatchResourceSnapshotBatchesAsync(
        long afterSequence,
        [EnumeratorCancellation] CancellationToken cancellationToken = default)
    {
        EnsureInitialLoadComplete();
        var updateSignal = _updateSignal ?? throw new InvalidOperationException("Resource update buffering was not enabled for this watcher.");
        if (Interlocked.Exchange(ref _updateConsumerClaimed, 1) != 0)
        {
            throw new InvalidOperationException("Resource snapshot updates support only one consumer for the lifetime of this watcher.");
        }

        await foreach (var _ in updateSignal.Reader.ReadAllAsync(cancellationToken).ConfigureAwait(false))
        {
            ResourceSnapshotUpdate[] updates;
            bool isResync;
            lock (_resourcesLock)
            {
                if (_resyncPending)
                {
                    updates = _resources.Values
                        .Select(snapshot => new ResourceSnapshotUpdate(_updateSequence, snapshot))
                        .ToArray();
                    _resyncPending = false;
                    isResync = true;

View on GitHub (pinned to 25830f84bd)