nautechsystems/nautilus_trader · error

Failed to start Coinbase WebSocket handler task: {e}

Error message

Failed to start Coinbase WebSocket handler task: {e}

What it means

connect() spawns the long-running WebSocket handler task with tokio::spawn; if the spawn call itself returns Err (the runtime cannot start the task), the adapter rolls back out_rx and returns this error.

Source

Thrown at crates/adapters/coinbase/src/websocket/client.rs:377

                            log::debug!("Output channel closed: {e}");
                            break;
                        }
                    }
                    Some(msg) => {
                        if let Err(e) = out_tx.send(msg) {
                            log::debug!("Output channel closed: {e}");
                            break;
                        }
                    }
                    None => {
                        log::debug!("Feed handler stopped");
                        break;
                    }
                }
            }
        }) {
            self.out_rx = None;
            anyhow::bail!("Failed to start Coinbase WebSocket handler task: {e}");
        }

        Ok(())
    }

    /// Subscribes to a channel for the given product IDs.
    pub async fn subscribe(
        &self,
        channel: CoinbaseWsChannel,
        product_ids: &[Ustr],
    ) -> anyhow::Result<()> {
        let jwt = if channel.requires_auth() {
            let credential = self
                .credential
                .as_ref()
                .ok_or_else(|| anyhow::anyhow!("Credentials required for {channel}"))?;
            Some(credential.build_ws_jwt()?)
        } else {

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure connect() is awaited inside a live tokio runtime (e.g. #[tokio::main] or a Handle)
  2. Delay adapter shutdown until WebSocket disconnect() finishes
  3. If spawning from a plain thread, obtain a tokio::runtime::Handle and use handle.spawn

Example fix

// before
std::thread::spawn(|| ws.connect().await); // no runtime
// after
let handle = tokio::runtime::Handle::current();
handle.spawn(async move { ws.connect().await });
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure a runtime exists before constructing/connecting the client
let handle = tokio::runtime::Handle::try_current()
    .map_err(|_| "Coinbase WebSocket requires a tokio runtime")?;

Try / catch

match tokio::runtime::Handle::try_current() {
    Ok(h) => { /* safe to call connect().await within runtime */ }
    Err(_) => { /* build/enter a runtime or use handle.spawn */ }
}

Prevention

When it happens

Trigger: tokio::spawn fails — practically only when there is no active Tokio runtime context or the runtime is shutting down while connect() is invoked.

Common situations: Calling connect() outside an async runtime (blocking context without a runtime handle); application shutdown tearing down the runtime while the adapter reconnects; blocking the runtime so shutdown aborts spawn.

Related errors


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