{"record":{"id":"9e102d9e2daf8085","repo":"dotnet/aspnetcore","slug":"the-connection-was-stopped-during-negotiation","errorCode":null,"errorMessage":"The connection was stopped during negotiation.","messagePattern":"The connection was stopped during negotiation\\.","errorType":"exception","errorClass":"AbortError","httpStatus":null,"severity":"warning","filePath":"src/SignalR/clients/ts/signalr/src/HttpConnection.ts","lineNumber":272,"sourceCode":"            if (this._options.skipNegotiation) {\n                if (this._options.transport === HttpTransportType.WebSockets) {\n                    // No need to add a connection ID in this case\n                    this.transport = this._constructTransport(HttpTransportType.WebSockets);\n                    // We should just call connect directly in this case.\n                    // No fallback or negotiate in this case.\n                    await this._startTransport(url, transferFormat);\n                } else {\n                    throw new Error(\"Negotiation can only be skipped when using the WebSocket transport directly.\");\n                }\n            } else {\n                let negotiateResponse: INegotiateResponse | null = null;\n                let redirects = 0;\n\n                do {\n                    negotiateResponse = await this._getNegotiationResponse(url);\n                    // the user tries to stop the connection when it is being started\n                    if (this._connectionState === ConnectionState.Disconnecting || this._connectionState === ConnectionState.Disconnected) {\n                        throw new AbortError(\"The connection was stopped during negotiation.\");\n                    }\n\n                    if (negotiateResponse.error) {\n                        throw new Error(negotiateResponse.error);\n                    }\n\n                    if ((negotiateResponse as any).ProtocolVersion) {\n                        throw new Error(\"Detected a connection attempt to an ASP.NET SignalR Server. This client only supports connecting to an ASP.NET Core SignalR Server. See https://aka.ms/signalr-core-differences for details.\");\n                    }\n\n                    if (negotiateResponse.url) {\n                        url = negotiateResponse.url;\n                    }\n\n                    if (negotiateResponse.accessToken) {\n                        // Replace the current access token factory with one that uses\n                        // the returned access token\n                        this._setTransportAccessToken(negotiateResponse.accessToken);","sourceCodeStart":254,"sourceCodeEnd":290,"githubUrl":"https://github.com/dotnet/aspnetcore/blob/3600ca084e9c8b5f4174fc5e747f4c52d2100806/src/SignalR/clients/ts/signalr/src/HttpConnection.ts#L254-L290","documentation":"During the negotiate loop, after each /negotiate round-trip the client checks _connectionState. If the user called stop() (or the connection was otherwise torn down) while negotiation was in flight, the state is Disconnecting/Disconnected and the client throws AbortError to abandon startup cleanly.","triggerScenarios":"User calls connection.stop() (or start() was racing with a stop) while HttpConnection is still waiting on the negotiate HTTP response. Also possible if the underlying connection silently disconnected and the state machine flipped during the await.","commonSituations":"A 'cancel' button or route-change handler that calls stop() immediately after start() on a slow network. A component unmount in React that aborts the connection mid-handshake. Reactive code that starts and immediately stops the connection based on a flag.","solutions":["Treat AbortError specially in your start() catch block and do not surface it as a real error.","Avoid calling stop() until start() has resolved; gate stop() on the start promise.","If you must support stop-during-start, swallow AbortError silently in the UI."],"exampleFix":"// before\nconst startPromise = connection.start();\n// user cancels immediately\nconnection.stop();\nawait startPromise; // throws AbortError\n\n// after\nconst startPromise = connection.start();\nawait startPromise;\n// only stop after start resolved\nawait connection.stop();\n\n// or, in the catch:\ntry { await connection.start(); }\ncatch (e) {\n  if (e instanceof signalR.AbortError) return; // expected on cancel\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"let startPromise: Promise<void> | null = null;\nasync function safeStart(conn: signalR.HubConnection) {\n  startPromise = conn.start();\n  await startPromise;\n  startPromise = null;\n}\nasync function safeStop(conn: signalR.HubConnection) {\n  if (startPromise) await startPromise.catch(() => {});\n  await conn.stop();\n}","typeGuard":"import { AbortError } from \"@microsoft/signalr\";\nfunction isAbortError(e: unknown): e is AbortError { return e instanceof AbortError; }","tryCatchPattern":"import { AbortError } from \"@microsoft/signalr\";\ntry { await connection.start(); }\ncatch (e) {\n  if (e instanceof AbortError) return; // expected when stop() raced start()\n  throw e;\n}","preventionTips":["Await the start() promise before calling stop().","Treat AbortError as expected during cancellation flows.","Use a single state machine for connection lifecycle to avoid start/stop races."],"tags":["negotiation","connection-state","lifecycle","abort"],"backgroundTag":null,"analyzedSha":"3600ca084e9c8b5f4174fc5e747f4c52d2100806","analyzedAt":"2026-08-11T16:32:30.678Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}