nautechsystems/nautilus_trader · error

WS handler command sender unavailable

Error message

WS handler command sender unavailable

What it means

The Kraken spot orders dispatcher has no command sender (`cmd_tx`) available, so it cannot forward the serialized order request to the WebSocket handler task. `cmd_tx()` returns None when the WS handler command channel has not been established (e.g. not connected) or has been dropped. The order is aborted before being inserted into `pending`.

Source

Thrown at crates/adapters/kraken/src/websocket/dispatch/spot_orders.rs:262

    fn send(
        self: &Arc<Self>,
        mut envelope: KrakenWsRequest,
        mut identity: PendingRequest,
        ts_now_ns: u64,
    ) -> anyhow::Result<u64> {
        let req_id = self.next_req_id();
        envelope.req_id = Some(req_id);
        identity.ts_sent_ns = ts_now_ns;

        let payload = SecretString::from(
            serde_json::to_string(&envelope)
                .map_err(|e| anyhow::anyhow!("serialize WS order request: {e}"))?,
        );

        let cmd_tx = self
            .cmd_tx()
            .ok_or_else(|| anyhow::anyhow!("WS handler command sender unavailable"))?;

        self.pending.insert(req_id, identity);

        if let Err(e) = cmd_tx.send(SpotHandlerCommand::SendOrderRequest { req_id, payload }) {
            self.pending.remove(&req_id);
            anyhow::bail!("handler command channel closed: {e}");
        }

        let state_for_timeout = Arc::downgrade(self);
        let task_spawner = self.task_spawner.read().clone();
        let cancel = task_spawner.cancellation_token();
        let timeout = self.timeout;

        if let Err(e) = task_spawner.spawn(async move {
            tokio::select! {
                biased;
                () = cancel.cancelled() => {
                    if let Some(state) = state_for_timeout.upgrade() {

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure the Kraken WebSocket client/handler is started and connected before submitting orders.
  2. Check adapter logs for prior disconnect/shutdown events and re-establish the WS connection.
  3. Add connection-state checks or retry logic so orders are only sent when the WS handler is live.
  4. If the handler task panicked or exited, inspect its logs and restart the adapter/client.
Defensive patterns

Strategy: validation

Validate before calling

// Only send when the WS handler is connected
if !ws_client.is_connected() { log::warn!("Kraken WS not connected; skipping order send"); return; }

Type guard

fn has_cmd_tx(dispatcher: &SpotOrdersDispatcher) -> bool {
    dispatcher.cmd_tx().is_some()
}

Try / catch

match dispatcher.send(order) {
    Ok(()) => {},
    Err(e) if e.to_string().contains("command sender unavailable") => reconnect_and_resubmit(),
    Err(e) => log::error!("order send failed: {e}"),
}

Prevention

When it happens

Trigger: Calling `send` on the spot orders dispatcher before the WebSocket connection/handler is started, or after the handler task has shut down and dropped the receiving end of the command channel.

Common situations: Submitting an order during adapter startup before the WS client is fully connected; orders submitted after a disconnect or shutdown of the WS handler; lifecycle races where the account/executor is live while the WS handler is not.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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