dotnet/aspnetcore · error · RuntimeException
Negotiation can only be skipped when using the WebSocket tra
Error message
Negotiation can only be skipped when using the WebSocket transport directly with '.withTransport(TransportEnum.WEBSOCKETS)' on the 'HubConnectionBuilder'.
What it means
A RuntimeException thrown during start() when skipNegotiate is enabled (via .withSkipNegotiate() on the builder) but the chosen transport is not WebSocket. Negotiation can be skipped only for the WebSocket transport because other transports (LongPolling) require the connection token obtained from negotiate. The guard prevents an invalid configuration from proceeding.
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 294cab2f9b)
Solutions
- Add .withTransport(TransportEnum.WEBSOCKETS) to the builder when using .withSkipNegotiate(true) / skipNegotiate.
- If you need LongPolling, remove skipNegotiate so the normal negotiate flow runs.
- Re-read the builder configuration to confirm transport and skipNegotiate are consistent.
Example fix
// before: skipNegotiate without pinning WebSocket
HubConnection conn = HubConnectionBuilder.create(url)
.withSkipNegotiate(true)
.withTransport(TransportEnum.LONG_POLLING)
.build();
// after: skipNegotiate requires WebSockets
HubConnection conn = HubConnectionBuilder.create(url)
.withSkipNegotiate(true)
.withTransport(TransportEnum.WEBSOCKETS)
.build(); Defensive patterns
Strategy: validation
Validate before calling
// Validate builder configuration before build/start.
boolean skipNegotiate = ...; TransportEnum te = ...;
if (skipNegotiate && te != TransportEnum.WEBSOCKETS) {
throw new IllegalStateException("skipNegotiate requires TransportEnum.WEBSOCKETS");
} Try / catch
try {
connection.start().blockingAwait();
} catch (RuntimeException e) {
if (e.getMessage().contains("Negotiation can only be skipped")) {
// rebuild with WebSocket transport or remove skipNegotiate
} else { throw e; }
} Prevention
- When using skipNegotiate, always pair it with .withTransport(TransportEnum.WEBSOCKETS).
- Centralize builder construction in one factory method that enforces consistent options.
- Add a unit test asserting builder options are consistent before start().
When it happens
Trigger: Building a HubConnection with .shouldSkipNegotiate(true) (or equivalent) while also selecting TransportEnum.LONG_POLLING or TransportEnum.ALL via .withTransport(...), then calling start().
Common situations: A developer enables skipNegotiate for performance or to bypass a negotiate endpoint that is blocked, but forgets to also pin the transport to WEBSOCKETS. Copying a builder snippet from one context (WebSocket-only service) into another that expects LongPolling. Misunderstanding that skipNegotiate implies direct WebSocket connection.
Related errors
- There were no compatible transports on the server.
- Negotiation can only be skipped when using the WebSocket tra
- Negotiate redirection limit exceeded.
- Message is incomplete.
- A valid url is required.
AI-assisted analysis of dotnet/aspnetcore@294cab2f9b (2026-08-06).
Data as JSON: /api/errors/4f8278baad66f235.
Report an issue: GitHub.