{"record":{"id":"5c0ea58fd15fc97c","repo":"OrchardCMS/OrchardCore","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/OrchardCore.Modules/OrchardCore.SignalR/wwwroot/Scripts/signalr.js","lineNumber":2844,"sourceCode":"                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                }\n                else {\n                    throw new Error(\"Negotiation can only be skipped when using the WebSocket transport directly.\");\n                }\n            }\n            else {\n                let negotiateResponse = null;\n                let redirects = 0;\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 === \"Disconnecting\" /* ConnectionState.Disconnecting */ || this._connectionState === \"Disconnected\" /* ConnectionState.Disconnected */) {\n                        throw new AbortError(\"The connection was stopped during negotiation.\");\n                    }\n                    if (negotiateResponse.error) {\n                        throw new Error(negotiateResponse.error);\n                    }\n                    if (negotiateResponse.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                    if (negotiateResponse.url) {\n                        url = negotiateResponse.url;\n                    }\n                    if (negotiateResponse.accessToken) {\n                        // Replace the current access token factory with one that uses\n                        // the returned access token\n                        const accessToken = negotiateResponse.accessToken;\n                        this._accessTokenFactory = () => accessToken;\n                        // set the factory to undefined so the AccessTokenHttpClient won't retry with the same token, since we know it won't change until a connection restart\n                        this._httpClient._accessToken = accessToken;\n                        this._httpClient._accessTokenFactory = undefined;","sourceCodeStart":2826,"sourceCodeEnd":2862,"githubUrl":"https://github.com/OrchardCMS/OrchardCore/blob/4306c0717fe573f6fca1b4955909ddab6a192807/src/OrchardCore.Modules/OrchardCore.SignalR/wwwroot/Scripts/signalr.js#L2826-L2862","documentation":"During startInternal's negotiate loop, the client re-checks its connection state after each negotiate HTTP call. If the user called stop() while negotiation was still in flight (state moved to Disconnecting or Disconnected), the client aborts startup with an AbortError carrying this message instead of continuing to build a transport on a dead connection.","triggerScenarios":"Calling hubConnection.stop() (or startInternal being cancelled) while the initial start()'s POST /hub/negotiate request is still pending; rapid start-then-stop sequences; component unmount tearing down the connection during page load.","commonSituations":"React/Vue/Angular components unmounting before SignalR finishes connecting; user navigating away during app boot; watchdog code that force-stops 'stuck' connections while the first negotiate request is slow.","solutions":["Treat this as an expected cancellation: catch AbortError and do not retry or log as failure.","Guard teardown so stop() is only called when a start is intended to be cancelled, and await start() before issuing stop() where possible.","Debounce or cancel component-level connect logic on unmount.","Increase the negotiate timeout / check server responsiveness if negotiate requests routinely hang long enough for stop() to interleave."],"exampleFix":"// before\ntry {\n  await connection.start();\n} catch (err) {\n  console.error(err); // noisy AbortError on unmount\n}\n// after\ntry {\n  await connection.start();\n} catch (err) {\n  if (err instanceof signalR.AbortError || err.name === \"AbortError\") {\n    return; // stop() was called during negotiation — expected\n  }\n  console.error(err);\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isAbortError(err) {\n  return err && (err.name === \"AbortError\" || err instanceof signalR.AbortError);\n}","tryCatchPattern":"try {\n  await connection.start();\n} catch (err) {\n  if (err.name === \"AbortError\") {\n    return; // stop() raced with start() during negotiation — expected\n  }\n  throw err;\n}","preventionTips":["Always await start() before calling stop() in teardown paths.","Track a 'starting' flag so stop() during startup is intentional and handled.","In component unmount handlers, catch and swallow AbortError.","Never let a watchdog auto-stop a connection that is still negotiating without logging."],"tags":["signalr","cancellation","race-condition","lifecycle"],"backgroundTag":"operation-cancelled","analyzedSha":"4306c0717fe573f6fca1b4955909ddab6a192807","analyzedAt":"2026-09-13T17:41:05.024Z","contentChangedAt":"2026-09-13T17:41:05.024Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}