dotnet/aspnetcore · critical · Error

Cannot start a disposed circuit.

Error message

Cannot start a disposed circuit.

What it means

Thrown by CircuitManager.start() when isDisposedOrDisposing() is true, i.e. the dispose pipeline (_disposePromise) has already begun. A Blazor Server circuit is a single-use SignalR-backed session; once dispose() is called the circuit is torn down and cannot be restarted, so calling start() again is treated as a programming error rather than a no-op.

Source

Thrown at src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts:88

    componentManager: RootComponentManager<ServerComponentDescriptor>,
    appState: string,
    options: CircuitStartOptions,
    logger: ConsoleLogger,
    eventRegistry: JSEventRegistry,
  ) {
    this._circuitId = undefined;
    this._applicationState = appState;
    this._componentManager = componentManager;
    this._options = options;
    this._logger = logger;
    this._eventRegistry = eventRegistry;
    this._renderQueue = new RenderQueue(this._logger);
    this._dispatcher = DotNet.attachDispatcher(this);
  }

  public start(): Promise<boolean> {
    if (this.isDisposedOrDisposing()) {
      throw new Error('Cannot start a disposed circuit.');
    }

    if (!this._startPromise) {
      this._startPromise = this.startCore();
    }

    return this._startPromise;
  }

  public updateRootComponents(operations: string, serverState: string): Promise<void> | undefined {
    if (this._isFirstRender) {
      this._isFirstRender = false;
      return this._connection?.send('UpdateRootComponents', operations, this._applicationState);
    } else {
      return this._connection?.send('UpdateRootComponents', operations, serverState);
    }
  }

View on GitHub (pinned to 294cab2f9b)

Solutions

  1. Do not call start() after dispose(); treat a disposed circuit as terminal and reload the page instead.
  2. If you manage Blazor lifecycle manually, track disposed state yourself and short-circuit start attempts.
  3. Guard re-initialization by checking window.Blazor existence before calling Blazor.start().

Example fix

// before
blazor.dispose();
await blazor.start(); // throws

// after
blazor.dispose();
location.reload(); // fresh circuit instead of restart
Defensive patterns

Strategy: validation

Validate before calling

// Before re-initializing, check Blazor is not already disposed/started.
if (window.Blazor && !blazorDisposed) {
  await Blazor.start();
}

Try / catch

try { await blazor.start(); }
catch (e) {
  if (/disposed circuit/i.test(e.message)) { location.reload(); }
  else { throw e; }
}

Prevention

When it happens

Trigger: Calling Blazor.start() (or otherwise invoking circuitManager.start()) after the page/session already called dispose() — e.g. navigating away and back in an SPA host, a hot-reload path that re-initializes Blazor, or manual teardown followed by an attempted restart.

Common situations: HMR/dev tooling that tears down and rebuilds the Blazor root; integration tests that dispose the circuit then re-invoke start; custom hosts that double-initialize Blazor on route changes.

Related errors


AI-assisted analysis of dotnet/aspnetcore@294cab2f9b (2026-08-06). Data as JSON: /api/errors/906c5e7161c566a3. Report an issue: GitHub.