dotnet/aspnetcore · error · RuntimeException
Negotiation can only be skipped when using the WebSocket…
Error message
Negotiation can only be skipped when using the WebSocket transport directly with '.withTransport(TransportEnum.WEBSOCKETS)' on the 'HubConnectionBuilder'.
What it means
Thrown during start() when skipNegotiate is true but the configured transportEnum is not WEBSOCKETS. Negotiation can be skipped only when connecting straight to WebSockets, because the negotiate response is what tells the client which of the other transports (LongPolling) the server supports. Any other transport requires the negotiate handshake first.
Solutions
- If you must skip negotiate, also pin the transport: .withTransport(TransportEnum.WEBSOCKETS).withSkipNegotiate(true).
- Otherwise, allow negotiate: do not call withSkipNegotiate(true) when using LongPolling or ALL.
- Re-read the builder chain and confirm skipNegotiate and transportEnum are consistent.
Example fix
// before HubConnection conn = HubConnectionBuilder.create(url) .withTransport(TransportEnum.LONG_POLLING) .withSkipNegotiate(true) // throws on start() .build(); // after (option A: keep skip, pin websockets) HubConnection conn = HubConnectionBuilder.create(url) .withTransport(TransportEnum.WEBSOCKETS) .withSkipNegotiate(true) .build(); // after (option B: keep long polling, run negotiate) HubConnection conn = HubConnectionBuilder.create(url) .withTransport(TransportEnum.LONG_POLLING) .build(); // skipNegotiate defaults to false
Defensive patterns
Strategy: validation
Validate before calling
HubConnection build(boolean skipNegotiate, TransportEnum transport, String url) {
if (skipNegotiate && transport != TransportEnum.WEBSOCKETS) {
throw new IllegalArgumentException("skipNegotiate requires WebSockets");
}
HubConnectionBuilder b = HubConnectionBuilder.create(url);
b.withTransport(transport);
if (skipNegotiate) b.withSkipNegotiate(true);
return b.build();
} Type guard
boolean skipNegotiateCompatible(TransportEnum t) {
return t == TransportEnum.WEBSOCKETS;
} Try / catch
try {
conn.start().blockingAwait();
} catch (RuntimeException e) {
if (e.getMessage() != null && e.getMessage().contains("Negotiation can only be skipped")) {
// either set transport to WEBSOCKETS, or remove withSkipNegotiate(true)
} else throw e;
} Prevention
- Only combine skipNegotiate with TransportEnum.WEBSOCKETS.
- Review the builder chain to ensure transport and skipNegotiate are consistent.
- If using LongPolling/ALL, leave negotiate enabled (default).
When it happens
Trigger: HubConnectionBuilder is configured with .withTransport(TransportEnum.LONG_POLLING) (or ALL) AND skipNegotiate=true. The branch in start() checks this combination and throws before opening a transport.
Common situations: Copy/paste from a WebSockets-only sample that set skipNegotiate; misconfigured builder that combined .shouldSkipNegotiate() with a non-WebSocket transport; trying to optimize connection time by skipping negotiate but keeping LongPolling as a fallback.
Related errors
- Negotiate redirection limit exceeded.
- There were no compatible transports on the server.
- A valid url is required.
- Error reading length header.
- Error reading NegotiateResponse
AI-assisted analysis of dotnet/aspnetcore@3600ca084e (2026-08-11).
Data as JSON: /api/errors/4f8278baad66f235.
Report an issue: GitHub.
Appendix: source
Thrown at src/SignalR/clients/java/signalr/core/src/main/java/com/microsoft/signalr/HubConnection.java:276
tokenCompletable.onError(error);
});
Single<NegotiateResponse> negotiate = null;
if (!skipNegotiate) {
negotiate = tokenCompletable.andThen(Single.defer(() -> startNegotiate(baseUrl, 0, localHeaders)));
} else {
negotiate = tokenCompletable.andThen(Single.defer(() -> Single.just(new NegotiateResponse(baseUrl))));
}
negotiate.flatMapCompletable(negotiateResponse -> {
logger.debug("Starting HubConnection.");
Transport transport = customTransport;
if (transport == null) {
Single<String> tokenProvider = negotiateResponse.getAccessToken() != null ? Single.just(negotiateResponse.getAccessToken()) : accessTokenProvider;
TransportEnum chosenTransport;
if (this.skipNegotiate) {
if (this.transportEnum != TransportEnum.WEBSOCKETS) {
throw new RuntimeException("Negotiation can only be skipped when using the WebSocket transport directly with '.withTransport(TransportEnum.WEBSOCKETS)' on the 'HubConnectionBuilder'.");
}
chosenTransport = this.transportEnum;
} else {
chosenTransport = negotiateResponse.getChosenTransport();
}
switch (chosenTransport) {
case LONG_POLLING:
transport = new LongPollingTransport(localHeaders, httpClient, tokenProvider);
break;
default:
transport = new WebSocketTransport(localHeaders, httpClient);
}
}
connectionState.transport = transport;
transport.setOnReceive(this.callback);
transport.setOnClose((message) -> stopConnection(message));View on GitHub (pinned to 3600ca084e)