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
- Construct ResourceSnapshotWatcher with update buffering enabled (the option that allocates the update signal channel).
- Create a dedicated watcher instance for streaming updates instead of reusing a polling-only watcher.
- 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
- Enable update buffering when constructing a watcher you intend to stream from
- Keep polling and streaming watchers as separate instances
- Document per-instance capabilities in your wrapper around ResourceSnapshotWatcher
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
- Resource snapshot updates support only one consumer for the…
- Already connected to
- Cannot reconnect: no previous connection.
- Initial resource snapshot load has not completed. Call…
- Already connected to AppHost backchannel.
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)