zeroclaw-labs/zeroclaw · error

WeCom WebSocket not connected

Error message

WeCom WebSocket not connected

What it means

Before sending any WS frame the channel waits for the outbound sender handle (ws_tx), polling every 100 ms (WECOM_WS_READY_POLL_MILLIS) up to WECOM_WS_READY_WAIT_SECS = 10 seconds. If the WebSocket session never establishes — or dropped and cleared ws_tx — before the deadline, this error bails. It is a readiness error, not a rejection of the message.

Source

Thrown at crates/zeroclaw-channels/src/wecom_ws.rs:376

    }

    fn static_policy_resolver(
        config: &WeComWsConfig,
    ) -> Arc<dyn Fn() -> WeComWsRuntimePolicy + Send + Sync> {
        let policy = WeComWsRuntimePolicy::from_config(config, std::iter::empty());
        Arc::new(move || policy.clone())
    }

    async fn wait_for_ws_sender(&self) -> Result<mpsc::Sender<WsOutbound>> {
        let deadline = Instant::now() + Duration::from_secs(WECOM_WS_READY_WAIT_SECS);

        loop {
            if let Some(tx) = self.ws_tx.lock().await.as_ref().cloned() {
                return Ok(tx);
            }

            if Instant::now() >= deadline {
                anyhow::bail!("WeCom WebSocket not connected");
            }

            tokio::time::sleep(Duration::from_millis(WECOM_WS_READY_POLL_MILLIS)).await;
        }
    }

    /// Send a JSON frame through the WebSocket outbound channel.
    async fn ws_send_frame(&self, frame: Value) -> Result<()> {
        let tx = self.wait_for_ws_sender().await?;
        tx.send(WsOutbound::Frame(frame))
            .await
            .map_err(|e| anyhow::Error::msg(format!("WeCom WS outbound channel closed: {e}")))
    }

    async fn ws_send_frame_and_wait_for_response(
        &self,
        frame: Value,
        req_id: &str,

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Confirm the channel's listen/connect task is running and check its logs for the underlying connect failure (auth, TLS, network) — fix that first
  2. Delay or retry the send with backoff across the reconnect window; once ws_tx appears the same send succeeds
  3. Verify outbound wss connectivity to the WeCom endpoint (proxy/firewall rules)
  4. If it recurs on every send, the connection task is crash-looping — inspect it rather than retrying sends

Example fix

// before
channel.send(msg).await?; // races WS connect at startup
// after
let mut n = 0;
loop {
    match channel.send(msg.clone()).await {
        Ok(_) => break,
        Err(e) if n < 5 && e.to_string().contains("not connected") => {
            n += 1; tokio::time::sleep(Duration::from_secs(2)).await;
        }
        Err(e) => return Err(e),
    }
}
Defensive patterns

Strategy: retry

Try / catch

Catch 'WeCom WebSocket not connected' and retry the send with a few seconds of backoff to ride out the 10-second readiness window and reconnects; if retries exhaust, surface a connection-down alert instead of failing the message silently.

Prevention

When it happens

Trigger: Dispatching a send/reply before listen() has completed the WeCom WS handshake; the WS connect failed (bad token/aeskey, network block) so ws_tx is never set; sending during a reconnect window after the connection dropped.

Common situations: Startup races (bot greets immediately on spawn); WeCom WS gateway unreachable due to firewall/proxy blocking wss; invalid credentials making connect fail silently; traffic sent mid-reconnect.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/da83d63035b86905. Report an issue: GitHub.