nautechsystems/nautilus_trader · error · anyhow::Error

Failed to send SetClient command: {e}

Error message

Failed to send SetClient command: {e}

What it means

Raised in HyperliquidWebSocketClient::connect_locked when the mpsc send of HandlerCommand::SetClient to the background WebSocket handler task fails. A failed send means the handler task's command-channel receiver has already been dropped, i.e. the handler exited or was never spawned, so the client cannot be registered with it and the connection cannot proceed. The client releases reserved rate-limiter slots before bailing so retries start from a clean state.

Source

Thrown at crates/adapters/hyperliquid/src/websocket/client.rs:359

            control.register(move || handle.request_reconnect());
        }

        // Create channels for handler communication
        let (cmd_tx, cmd_rx) = tokio::sync::mpsc::unbounded_channel::<HandlerCommand>();
        let (out_tx, out_rx) = tokio::sync::mpsc::unbounded_channel::<NautilusWsMessage>();

        // Update cmd_tx before connection_mode to avoid race where is_active() returns
        // true but subscriptions still go to the old placeholder channel
        *self.cmd_tx.write().await = cmd_tx.clone();
        self.out_rx = Some(out_rx);

        self.connection_mode.store(client.connection_mode_atomic());
        log::debug!("Hyperliquid WebSocket connected: {}", self.url);

        // Send SetClient command immediately
        if let Err(e) = cmd_tx.send(HandlerCommand::SetClient(client)) {
            self.release_limit_reservations();
            anyhow::bail!("Failed to send SetClient command: {e}");
        }

        // Initialize handler with existing instruments
        let instruments_vec: Vec<InstrumentAny> =
            self.instruments.load().values().cloned().collect();

        if !instruments_vec.is_empty()
            && let Err(e) = cmd_tx.send(HandlerCommand::InitializeInstruments(instruments_vec))
        {
            log::error!("Failed to send InitializeInstruments: {e}");
        }

        for (coin, uses) in self.trade_streams.snapshot() {
            if let Err(e) = cmd_tx.send(HandlerCommand::UpdateTradeSubs { coin, uses }) {
                log::error!("Failed to send UpdateTradeSubs: {e}");
            }
        }

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Recreate the WebSocket client (a fresh instance re-establishes the command channel and handler task) and retry connect().
  2. Check logs immediately before this error for handler-task panics or early exits and fix the underlying cause.
  3. Serialize connect()/disconnect() calls to avoid racing shutdown against connection setup.
  4. Verify no code path drops the command receiver prematurely during setup.

Example fix

// before: reuse stale client after disconnect -> channel closed / client.connect().await?; / // after: rebuild client so cmd channel + handler task are recreated / let mut client = HyperliquidWebSocketClient::new(url, ...); / client.connect().await?;
Defensive patterns

Strategy: try-catch

Validate before calling

fn handler_alive(cmd_tx: &tokio::sync::mpsc::Sender<HandlerCommand>) -> bool { !cmd_tx.is_closed() }

Type guard

fn handler_alive(cmd_tx: &tokio::sync::mpsc::Sender<HandlerCommand>) -> bool { !cmd_tx.is_closed() }

Try / catch

match client.connect().await { Err(e) if e.to_string().contains("Failed to send SetClient command") => { client = HyperliquidWebSocketClient::new(url, ...); client.connect().await?; } Err(e) => return Err(e), Ok(()) => {} }

Prevention

When it happens

Trigger: Calling connect() when the receiving half of cmd_tx has been dropped: after a previous disconnect, after the handler task panicked or completed early, or when the receiver end was closed before this send.

Common situations: Reconnecting a client after a prior disconnect without recreating the channel; a handler task crash caused by a malformed message; racing concurrent connect()/disconnect() calls so the task shuts down mid-connect; reusing a stale client instance across reconnect attempts.

Understand the failure class

Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/69696f16a9da084e. Report an issue: GitHub.