nautechsystems/nautilus_trader · error
Failed to start Polymarket WebSocket handler task: {e}
Error message
Failed to start Polymarket WebSocket handler task: {e} What it means
connect() spawns a tokio task that drives the Polymarket WebSocket read loop and forwards messages through out_rx. If spawning fails (e.g. the runtime is shutting down or no runtime is active), the method restores out_rx to None and aborts the connection attempt with this error.
Source
Thrown at crates/adapters/polymarket/src/websocket/client.rs:386
log::error!("Output channel closed, stopping handler");
}
break;
}
}
None => {
if handler.is_stopped() {
log::debug!("Stop signal received, ending handler task");
} else {
log::warn!("Polymarket WebSocket stream ended unexpectedly");
}
break;
}
}
}
log::debug!("Polymarket WebSocket handler task completed");
}) {
self.out_rx = None;
anyhow::bail!("Failed to start Polymarket WebSocket handler task: {e}");
}
Ok(())
}
fn websocket_config(&self) -> WebSocketConfig {
// The market endpoint rejects text PING before its initial subscription. Protocol pings
// keep an idle socket alive until FeedHandler starts the required text heartbeat.
let heartbeat_payload = match self.channel {
WsChannel::Market => None,
WsChannel::User => Some(POLYMARKET_HEARTBEAT_PAYLOAD.to_string()),
};
WebSocketConfig {
url: self.url.clone(),
headers: vec![],
heartbeat_interval_secs: Some(POLYMARKET_HEARTBEAT_SECS),
heartbeat_payload,
connect_timeout_ms: Some(15_000),View on GitHub (pinned to 18893faf8b)
Solutions
- Ensure connect() (and all client async calls) run inside an active, live tokio runtime
- Guard reconnection logic so it is cancelled during shutdown (select! on a shutdown signal before spawning)
- Check the wrapped error `e` — spawn failures usually mean JoinHandle/panics in the task body; fix the panic
- Retry the connect after runtime is confirmed healthy; verify out_rx was reset to None so state stays consistent
Example fix
// before
let handle = tokio::spawn(async { /* read loop */ });
// after
match tokio::runtime::Handle::try_current() {
Ok(h) => { let handle = h.spawn(async { /* read loop */ }); ... }
Err(e) => anyhow::bail!("no active tokio runtime: {e}"),
} Defensive patterns
Strategy: retry
Validate before calling
// ensure an active runtime before connect
if tokio::runtime::Handle::try_current().is_err() {
anyhow::bail!("PolymarketWebSocketClient::connect requires a live tokio runtime");
} Try / catch
match client.connect().await {
Ok(()) => {},
Err(e) if e.to_string().contains("Failed to start Polymarket WebSocket handler task") => {
tokio::time::sleep(Duration::from_millis(200)).await;
client.connect().await?; // retry once runtime confirmed healthy
}
Err(e) => return Err(e),
} Prevention
- Never spawn the client outside a tokio runtime
- Cancel reconnect loops during shutdown before tasks are dropped
- Check that the handler task body cannot panic
When it happens
Trigger: Calling connect() on a PolymarketWebSocketClient while the tokio runtime is being torn down, inside a runtime drop, or via tokio::spawn with no reactor (e.g. running async code outside #[tokio::main]/block_on).
Common situations: Graceful shutdown racing with a reconnect attempt; spawning the client in a plain std::thread without a runtime; nested runtime misuse with tokio::spawn.
Related errors
- failed to finish Binance Futures data command tasks: {e}
- Failed to start Coinbase WebSocket handler task: {e}
- conflicting RTDS TWAP observation topic={} symbol={} timesta
- Unsupported RTDS custom data type: {other}
- subscribe_market() requires a market-channel client (created
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/0a071de2fe218904.
Report an issue: GitHub.