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

  1. Add .withTransport(TransportEnum.WEBSOCKETS) to the builder when using .withSkipNegotiate(true) / skipNegotiate.
  2. If you need LongPolling, remove skipNegotiate so the normal negotiate flow runs.
  3. 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 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


AI-assisted analysis of dotnet/aspnetcore@294cab2f9b (2026-08-06). Data as JSON: /api/errors/4f8278baad66f235. Report an issue: GitHub.