{"record":{"id":"906c5e7161c566a3","repo":"dotnet/aspnetcore","slug":"cannot-start-a-disposed-circuit","errorCode":null,"errorMessage":"Cannot start a disposed circuit.","messagePattern":"Cannot start a disposed circuit\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts","lineNumber":88,"sourceCode":"    componentManager: RootComponentManager<ServerComponentDescriptor>,\n    appState: string,\n    options: CircuitStartOptions,\n    logger: ConsoleLogger,\n    eventRegistry: JSEventRegistry,\n  ) {\n    this._circuitId = undefined;\n    this._applicationState = appState;\n    this._componentManager = componentManager;\n    this._options = options;\n    this._logger = logger;\n    this._eventRegistry = eventRegistry;\n    this._renderQueue = new RenderQueue(this._logger);\n    this._dispatcher = DotNet.attachDispatcher(this);\n  }\n\n  public start(): Promise<boolean> {\n    if (this.isDisposedOrDisposing()) {\n      throw new Error('Cannot start a disposed circuit.');\n    }\n\n    if (!this._startPromise) {\n      this._startPromise = this.startCore();\n    }\n\n    return this._startPromise;\n  }\n\n  public updateRootComponents(operations: string, serverState: string): Promise<void> | undefined {\n    if (this._isFirstRender) {\n      this._isFirstRender = false;\n      return this._connection?.send('UpdateRootComponents', operations, this._applicationState);\n    } else {\n      return this._connection?.send('UpdateRootComponents', operations, serverState);\n    }\n  }\n","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/dotnet/aspnetcore/blob/3600ca084e9c8b5f4174fc5e747f4c52d2100806/src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts#L70-L106","documentation":"`CircuitManager.start()` bootstraps the SignalR circuit for a Blazor Server/Global app. Before doing any work it checks `isDisposedOrDisposing()`, which is true once `_disposePromise` is set (i.e. `dispose()` has been called). Starting a circuit that is already tearing down is a lifecycle violation, so it throws immediately rather than producing a half-initialized connection.","triggerScenarios":"Calling `circuitManager.start()` (directly or via a re-entrant `Blazor.start`/reconnect path) after `dispose()` has begun — `_disposePromise` is defined, so `isDisposedOrDisposing()` returns true.","commonSituations":"A tab being closed/unloaded while a manual reconnect or late `start()` is queued; SPA fast-back/forward caches (bfcache) replaying startup after the page already tore down; custom reconnection logic calling `start` in a retry loop without checking disposal; hot reload or render-mode switching disposing the old circuit while a deferred start runs.","solutions":["Guard any custom `start`/reconnect call with `isDisposedOrDisposing()` before invoking it.","Avoid calling `start()` more than once — it is idempotent via `_startPromise`, so reuse the existing promise instead of re-driving startup.","Cancel pending reconnect timers in your `dispose`/`onCircuitClosed` handler so they cannot fire after disposal.","For bfcache, listen for `pageshow` with `persisted` and do not re-start a disposed circuit; let a fresh page load build a new one."],"exampleFix":"// before\nasync function tryRestart(cm: CircuitManager) {\n  await cm.start(); // throws if already disposing\n}\n\n// after\nasync function tryRestart(cm: CircuitManager) {\n  if (cm.isDisposedOrDisposing()) return;\n  await cm.start();\n}","handlingStrategy":"validation","validationCode":"function safeStart(cm: CircuitManager) {\n  return cm.isDisposedOrDisposing() ? Promise.resolve(false) : cm.start();\n}","typeGuard":null,"tryCatchPattern":"try { await cm.start(); }\ncatch (e) { if (/disposed circuit/.test(e.message)) { /* circuit gone, do not restart */ return; } throw e; }","preventionTips":["Check isDisposedOrDisposing() before calling start/reconnect.","Reuse the existing start promise rather than re-driving start.","Cancel pending reconnect timers on dispose.","Handle bfcache pageshow(persisted) without re-starting a disposed circuit."],"tags":["circuit","lifecycle","blazor-server","signalr","dispose"],"backgroundTag":null,"analyzedSha":"3600ca084e9c8b5f4174fc5e747f4c52d2100806","analyzedAt":"2026-08-11T16:32:30.678Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}