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
- Recreate the WebSocket client (a fresh instance re-establishes the command channel and handler task) and retry connect().
- Check logs immediately before this error for handler-task panics or early exits and fix the underlying cause.
- Serialize connect()/disconnect() calls to avoid racing shutdown against connection setup.
- 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
- Treat client instances as single-use per connection lifecycle; rebuild on reconnect.
- Never call connect() on a client previously passed to disconnect().
- Serialize connection lifecycle calls to prevent disconnect racing connect.
- Monitor logs for handler-task exits so you reconnect before subscriptions fail.
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
- Failed to send UpdateInstrument command: {e}
- std::mem::take(&mut self.shutdown_errors).join("; ")
- WS setup error channel closed
- WebSocket output receiver not available
- Coinbase WebSocket handler failed: {error}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/69696f16a9da084e.
Report an issue: GitHub.