nautechsystems/nautilus_trader · error · anyhow::Error

Polymarket WebSocket handler failed: {error}

Error message

Polymarket WebSocket handler failed: {error}

What it means

disconnect() aborts the feed-handler task and joins it with a 2s timeout. TaskJoinOutcome::Failed means the handler task finished on its own but returned an Err; disconnect() surfaces that handler error as 'Polymarket WebSocket handler failed: {error}'. The {error} text is the handler's own failure cause (connection, auth, parse, etc.), so the real diagnosis lives in the wrapped message.

Source

Thrown at crates/adapters/polymarket/src/websocket/client.rs:456

    /// Disconnects the WebSocket connection.
    pub async fn disconnect(&mut self) -> anyhow::Result<()> {
        log::debug!("Disconnecting Polymarket WebSocket");
        self.signal.store(true, Ordering::Relaxed);

        if let Err(e) = self.cmd_tx.read().await.send(HandlerCommand::Disconnect) {
            log::debug!("Failed to send disconnect (handler may already be shut down): {e}");
        }

        let task_result = match finish_task(
            &mut self.task_handle,
            std::time::Duration::from_secs(2),
            std::time::Duration::from_secs(2),
        )
        .await
        {
            None | Some(TaskJoinOutcome::Completed(()) | TaskJoinOutcome::Aborted) => Ok(()),
            Some(TaskJoinOutcome::Failed(error)) => Err(anyhow::anyhow!(
                "Polymarket WebSocket handler failed: {error}"
            )),
            Some(TaskJoinOutcome::Incomplete) => Err(anyhow::anyhow!(
                "Polymarket WebSocket handler did not stop after abort"
            )),
        };
        // Invalidate after the task has stopped so any in-flight auth_tracker.succeed()
        // calls from the handler cannot race with and survive the invalidation.
        self.auth_tracker.invalidate();

        if let Some(control) = &self.socket_control {
            control.deregister();
        }
        log::debug!("Polymarket WebSocket disconnected");
        task_result
    }

    /// Returns `true` if the WebSocket is actively connected.

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Read the wrapped {error} in the message — it names the handler's actual failure; fix that root cause
  2. Treat it as non-fatal during shutdown if you only wanted the task gone: the session is stopped either way
  3. If auth-related, refresh API credentials before reconnecting
  4. Add retries/backoff on reconnect if the handler repeatedly dies from transient network errors

Example fix

// before
client.disconnect().await?; // propagates handler's own failure
// after
if let Err(e) = client.disconnect().await {
    log::warn!("disconnect reported handler failure (session stopped anyway): {e:#}");
}
Defensive patterns

Strategy: try-catch

Try / catch

match client.disconnect().await {
    Ok(()) => {},
    Err(e) if e.to_string().starts_with("Polymarket WebSocket handler failed") => {
        // handler already dead with its own error; session is stopped either way
        log::warn!("handler failed during disconnect: {e:#}");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling disconnect() while (or after) the handler task has terminated with an error — e.g. the WebSocket stream errored out, an auth call failed, or message parsing returned a fatal error that ended the handler's run loop.

Common situations: Shutdown racing a handler that just died from a network drop; credentials revoked mid-session so the handler exits with an auth error; a malformed venue message causing a fatal parse error in the handler loop.

Related errors


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