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
- Confirm the channel's listen/connect task is running and check its logs for the underlying connect failure (auth, TLS, network) — fix that first
- Delay or retry the send with backoff across the reconnect window; once ws_tx appears the same send succeeds
- Verify outbound wss connectivity to the WeCom endpoint (proxy/firewall rules)
- 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
- Defer outbound sends until the channel reports connected (subscribe to channel status/connection events)
- Supervise the WS task so it reconnects automatically and clears ws_tx only on real disconnects
- Do not fire sends from spawn-time hooks before the session exists
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
- wecom_ws channel is not connected
- WeCom WebSocket channel requires the `channel-wecom-ws` feat
- WeCom WebSocket stream_mode={} is not supported; use partial
- WeCom WS {command} response channel closed before ack (req_i
- WeCom WS {command} ack timeout after {}s (req_id={req_id})
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/da83d63035b86905.
Report an issue: GitHub.