nautechsystems/nautilus_trader · error

Failed to send SetClient command: {e}

Error message

Failed to send SetClient command: {e}

What it means

Raised when sending the HandlerCommand::SetClient command over the internal unbounded command channel fails. SetClient must reach the handler task before the connection is marked active; a send failure means the receiver side (handler task) is already gone, so startup cannot proceed.

Source

Thrown at crates/adapters/lighter/src/websocket/client.rs:516

            anyhow::bail!("Lighter WebSocket initial connection cancelled");
        }

        let (cmd_tx, cmd_rx) = tokio::sync::mpsc::unbounded_channel::<HandlerCommand>();
        let (out_tx, out_rx) = tokio::sync::mpsc::unbounded_channel::<NautilusWsMessage>();

        // Capture the connection-mode atomic before moving `client` into the
        // SetClient command below.
        let connection_mode_atomic = client.connection_mode_atomic();
        let connection_epoch_atomic = client.connection_epoch_atomic();

        // Queue SetClient (and the instrument cache replay) onto the new
        // command channel BEFORE publishing it to clones or marking the
        // connection active. Otherwise a clone observing `is_active()` could
        // race in and send a Subscribe before SetClient lands, and the
        // handler would drop the subscription because `inner == None`.
        let reconnect_handle = client.reconnect_handle();
        if let Err(e) = cmd_tx.send(HandlerCommand::SetClient(client)) {
            anyhow::bail!("Failed to send SetClient command: {e}");
        }

        if let Some(control) = &self.socket_control {
            control.register(move || reconnect_handle.request_reconnect());
        }

        let initial_instruments: Vec<(i16, InstrumentAny)> = self
            .instruments
            .iter()
            .map(|entry| (*entry.key(), entry.value().clone()))
            .collect();

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

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Look for a preceding panic log from the handler task — the receiver dying is the real cause; fix that panic first.
  2. Recreate the client and call connect() again instead of reusing a client whose handler died.
  3. Check the adapter version for known handler-task panics; upgrade if fixed upstream.
  4. Enable panic hooks/backtrace logging (RUST_BACKTRACE=1) to capture the handler crash that precedes this error.
  5. If reconnecting programmatically, ensure the old handler was fully drained (call disconnect()) before a new connect().

Example fix

// before: reusing a client after a crashed handler
client.connect().await?; // Failed to send SetClient command: channel closed

// after: build a fresh client after a failure
if client.is_failed() {
    client = build_ws_client(config)?;
}
client.connect().await?;
Defensive patterns

Strategy: try-catch

Try / catch

match client.connect().await {
    Err(e) if e.to_string().contains("Failed to send SetClient") => {
        log::error!("handler died before SetClient: {e:#}");
        let mut fresh = build_ws_client(cfg)?;
        fresh.connect().await?; // recreate client, don't reuse
    }
    r => r?,
}

Prevention

When it happens

Trigger: Calling connect() when the handler task consuming cmd_tx has already terminated (panicked or exited) before consuming the SetClient command, causing tokio mpsc send to return SendError.

Common situations: A handler task panic immediately after spawn (e.g. a bug in message handling of an initial frame); calling connect() on a client whose previous handler crashed; resource exhaustion killing the tokio task.

Related errors


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