nautechsystems/nautilus_trader · error · anyhow::Error

Failed to send SetDepth10Sub command: {e}

Error message

Failed to send SetDepth10Sub command: {e}

What it means

This error wraps a failed send of the SetDepth10Sub(subscribed: true) command on the client's internal mpsc command channel. It is thrown right after the UpdateInstrument registration during depth-10 subscription, so the handler task stopped accepting commands between the two sends. It indicates the WebSocket handler receiver is closed.

Source

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

        mantissa: Option<u32>,
    ) -> anyhow::Result<()> {
        let instrument = self
            .get_instrument(&instrument_id)
            .ok_or_else(|| InstrumentLookupError::not_found(instrument_id))?;
        let coin = instrument.raw_symbol().inner();

        let cmd_tx = self.cmd_tx.read().await;

        cmd_tx
            .send(HandlerCommand::UpdateInstrument(instrument.clone()))
            .map_err(|e| anyhow::anyhow!("Failed to send UpdateInstrument command: {e}"))?;

        cmd_tx
            .send(HandlerCommand::SetDepth10Sub {
                coin,
                subscribed: true,
            })
            .map_err(|e| anyhow::anyhow!("Failed to send SetDepth10Sub command: {e}"))?;

        self.send_book_stream_subscribe(&cmd_tx, coin, BookStreamUse::Depth10, n_sig_figs, mantissa)
    }

    /// Unsubscribe from order book depth-10 snapshots.
    ///
    /// Clears the depth10 emission flag and tears down the underlying
    /// `l2Book` stream unless active deltas subscribers still need it.
    pub async fn unsubscribe_book_depth10(
        &self,
        instrument_id: InstrumentId,
    ) -> anyhow::Result<()> {
        let instrument = self
            .get_instrument(&instrument_id)
            .ok_or_else(|| InstrumentLookupError::not_found(instrument_id))?;
        let coin = instrument.raw_symbol().inner();

        let cmd_tx = self.cmd_tx.read().await;

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Verify the handler task is alive (JoinHandle not finished) before subscribing; respawn/reconnect if it exited.
  2. Avoid calling subscribe concurrently with disconnect/shutdown; sequence them or guard with application-level state.
  3. Re-create the client if the handler loop exited; the channel cannot be revived.
  4. Check handler-task logs/panic hooks for the root cause of the loop exiting.

Example fix

// before
client.disconnect().await?;
client.subscribe_book_depth10(instrument_id).await?; // channel already closed

// after
if !client.is_connected() {
    client.connect().await?;
}
client.subscribe_book_depth10(instrument_id).await?;
Defensive patterns

Strategy: try-catch

Validate before calling

// rust
if !client.is_connected() {
    return Err(anyhow::anyhow!("client not connected before depth10 subscribe"));
}

Try / catch

// rust
if let Err(e) = client.subscribe_book_depth10(instrument_id).await {
    if e.to_string().contains("SetDepth10Sub") {
        tracing::warn!("handler channel closed ({e}); re-establishing client");
        client = reconnect_client().await?;
        client.subscribe_book_depth10(instrument_id).await?;
    } else { return Err(e); }
}

Prevention

When it happens

Trigger: Calling subscribe_book_depth10 when the handler task has already terminated — after disconnect(), after the handler task panicked or its JoinHandle/receiver was dropped, or on a client whose run loop exited between the preceding UpdateInstrument send and this one.

Common situations: Subscribe racing with shutdown/disconnect; handler task crashed earlier (check its logs); client used after being moved into a scope that ended; runtime shutdown during teardown.

Related errors


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