{"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":"critical","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/294cab2f9b2e03af6b953820c7ab497c3c8b7ad9/src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts#L70-L106","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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()."],"exampleFix":"// before\nblazor.dispose();\nawait blazor.start(); // throws\n\n// after\nblazor.dispose();\nlocation.reload(); // fresh circuit instead of restart","handlingStrategy":"validation","validationCode":"// Before re-initializing, check Blazor is not already disposed/started.\nif (window.Blazor && !blazorDisposed) {\n  await Blazor.start();\n}","typeGuard":null,"tryCatchPattern":"try { await blazor.start(); }\ncatch (e) {\n  if (/disposed circuit/i.test(e.message)) { location.reload(); }\n  else { throw e; }\n}","preventionTips":["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."],"tags":["blazor-server","circuit","lifecycle"],"analyzedSha":"294cab2f9b2e03af6b953820c7ab497c3c8b7ad9","analyzedAt":"2026-08-06T20:08:02.189Z","schemaVersion":2},"datasetVersion":"2026-08-06T23:17:07.152Z"}