nautechsystems/nautilus_trader · error

No active WebSocket client

Error message

No active WebSocket client

What it means

The streams WebSocket handler's send_text requires an active inner WS client; when self.inner is None the handler has no established connection to send on, so it bails. Messages can only be sent after a successful connect that populates inner.

Source

Thrown at crates/adapters/binance/src/spot/websocket/streams/handler.rs:292

                payload,
                Some(BINANCE_RATE_LIMIT_KEY_SUBSCRIPTION.as_slice()),
            )
            .await
        {
            self.pending_requests.take(request_id);
            return Err(e);
        }

        Ok(())
    }

    async fn send_text(
        &self,
        payload: String,
        rate_limit_keys: Option<&[Ustr]>,
    ) -> anyhow::Result<()> {
        let Some(client) = &self.inner else {
            anyhow::bail!("No active WebSocket client");
        };
        client
            .send_text(payload, rate_limit_keys)
            .await
            .map_err(|e| anyhow::anyhow!("Failed to send message: {e}"))?;
        Ok(())
    }
}

/// Classifies a JSON text frame that did not match a subscription response or
/// known error envelope. Recognizes the `serverShutdown` event; otherwise
/// emits `RawJson` for parseable payloads or an empty vector for garbage.
fn classify_unsolicited_json(text: &str) -> Vec<BinanceSpotWsMessage> {
    let Ok(value) = serde_json::from_str::<serde_json::Value>(text) else {
        log::warn!("Failed to parse JSON message: {text}");
        return vec![];
    };

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure connect() is awaited and succeeded before any subscribe/unsubscribe commands.
  2. Buffer or re-queue subscription commands during reconnection and replay once the client is active.
  3. Check logs for the disconnect that cleared inner; if unexpected, investigate connection stability.
  4. Guard the caller with a connected-state check before sending commands.

Example fix

// before
handler.send_text(payload, keys).await?;
// after
if !handler.is_connected().await {
    log::warn!("not connected, queuing subscribe command");
    pending_commands.push((payload, keys));
} else {
    handler.send_text(payload, keys).await?;
}
Defensive patterns

Strategy: type-guard

Type guard

async fn is_connected(handler: &Handler) -> bool { handler.inner.is_some() }

Try / catch

match handler.send_text(payload, keys).await {
    Err(e) if e.to_string().contains("No active WebSocket client") => {
        queue_for_replay((payload, keys)); // resend after reconnect
    }
    other => other?,
}

Prevention

When it happens

Trigger: handle_subscribe or handle_unsubscribe calling send_text before connect() completed, or after the inner client was cleared by disconnect/reconnect, or when a connection was never established because connect failed.

Common situations: Subscribing immediately after constructing the handler without connecting; a dropped connection cleared inner while subscription management still runs; reconnection windows where commands arrive between disconnect and reconnect.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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