{"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/294cab2f9b2e03af6b953820c7ab497c3c8b7ad9/src/SignalR/clients/ts/signalr/src/HttpConnection.ts#L254-L290","documentation":"Wrapped in AbortError at HttpConnection.ts:272 inside the negotiate loop of _startInternal. It fires when the connection state transitions to Disconnecting or Disconnected between negotiate round-trips, meaning `stop()` was called while a negotiate POST was still in flight. This is a deliberate race-resolution signal rather than a bug; the in-flight start promise rejects with this AbortError so callers awaiting `start()` observe the cancellation.","triggerScenarios":"Calling `connection.stop()` (or `hubConnection.stop()`) while `start()` is still awaiting the negotiate HTTP response; navigating away or closing the tab during the brief negotiate window; a reconnect attempt that is itself cancelled mid-negotiate.","commonSituations":"User navigates away or logs out immediately after connecting; an `onclose` handler that calls `stop()` re-entrantly; React/SPA unmount racing the connection start; tests that tear down a connection before start resolves.","solutions":["Treat AbortError specially in your start() catch handler — log and ignore it rather than surfacing as a hard failure.","Guard stop() calls with a check of connection.state so you don't interrupt an in-flight start.","In tests/unmount, await the start() promise (or race it with a timeout) before issuing stop()."],"exampleFix":"// before\ntry { await hub.start(); } catch (e) { console.error(\"start failed\", e); }\n\n// after\ntry { await hub.start(); } catch (e) {\n  if (e instanceof AbortError || /stopped during negotiation/i.test(e.message)) {\n    return; // expected when stop() raced start()\n  }\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"// no pre-check possible — the race is between start() and stop().\n// Just ensure you only call stop after start has resolved.","typeGuard":"function isAbortDuringNegotiate(e: unknown): boolean {\n  return e instanceof AbortError && /stopped during negotiation/i.test(String((e as Error).message));\n}","tryCatchPattern":"try {\n  await hub.start();\n} catch (e) {\n  if (e instanceof AbortError && /stopped during negotiation/i.test(e.message)) {\n    // expected cancellation — ignore\n    return;\n  }\n  throw e;\n}","preventionTips":["Don't call stop() in onclose handlers re-entrantly during start.","In component unmount, await start() (or race with timeout) before issuing stop().","Differentiate AbortError from real failures in your start catch handler."],"tags":["lifecycle","race-condition","negotiate","abort"],"analyzedSha":"294cab2f9b2e03af6b953820c7ab497c3c8b7ad9","analyzedAt":"2026-08-06T20:08:02.189Z","schemaVersion":2},"datasetVersion":"2026-08-06T23:17:07.152Z"}