nautechsystems/nautilus_trader · critical

handler command channel closed: {e}

Error message

handler command channel closed: {e}

What it means

When submitting a Kraken Spot order over WebSocket, the request is dispatched to the handler task through an mpsc command channel. If that channel's receiver has been dropped (the handler task has shut down), the send fails; the adapter removes the pending-request entry it just registered and surfaces the channel error.

Source

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

    ) -> 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() {
                        state.pending.remove(&req_id);
                    }
                }
                () = tokio::time::sleep(timeout) => {
                    let Some(state) = state_for_timeout.upgrade() else {
                        return;

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check WS connection state and reconnect before retrying the order submission.
  2. Implement an on-disconnect callback/hook that pauses order flow until the handler task is live again.
  3. Retry the order through a freshly (re)connected Kraken Spot WS client.
  4. Log and alert on this error — it usually indicates the adapter's background task died and needs supervision.

Example fix

// before
client.send_order(payload)?; // may fail after handler died
// after
if !client.is_handler_alive() { client.reconnect().await?; }
client.send_order(payload)?;
Defensive patterns

Strategy: try-catch

Validate before calling

if !ws_client.is_connected() || !ws_client.is_handler_alive() {
    ws_client.reconnect().await?;
}

Try / catch

match dispatch.send(req_id, payload) {
    Err(e) if e.to_string().contains("handler command channel closed") => {
        pending.remove(&req_id);
        reconnect_and_resubmit().await?;
    }
    r => r?,
}

Prevention

When it happens

Trigger: Calling send on the Kraken Spot order dispatch after the WS handler task has terminated — e.g. the connection was dropped/disconnected and the handler loop exited, but the dispatch handle is still being used to place an order.

Common situations: Ordering during a WebSocket disconnect or reconnect window; network outage killing the handler task while strategy keeps trading; shutting down the adapter while in-flight logic still attempts submissions.

Related errors


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