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
- Do not call start() after dispose(); treat a disposed circuit as terminal and reload the page instead.
- If you manage Blazor lifecycle manually, track disposed state yourself and short-circuit start attempts.
- 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
- Treat circuit dispose as terminal; reload instead of restart.
- Never call Blazor.start() more than once per page load.
- Track a disposed flag in host code to short-circuit re-init.
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
- Blazor Server has already started.
- Cannot start the circuit until Blazor Server has started.
- Circuit host not initialized.
- Received persisted state for circuit ID '${circuitId}', but
- The circuit associated with this dispatcher is no longer ava
AI-assisted analysis of dotnet/aspnetcore@294cab2f9b (2026-08-06).
Data as JSON: /api/errors/906c5e7161c566a3.
Report an issue: GitHub.