{"record":{"id":"da83d63035b86905","repo":"zeroclaw-labs/zeroclaw","slug":"wecom-websocket-not-connected","errorCode":null,"errorMessage":"WeCom WebSocket not connected","messagePattern":"WeCom WebSocket not connected","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/wecom_ws.rs","lineNumber":376,"sourceCode":"    }\n\n    fn static_policy_resolver(\n        config: &WeComWsConfig,\n    ) -> Arc<dyn Fn() -> WeComWsRuntimePolicy + Send + Sync> {\n        let policy = WeComWsRuntimePolicy::from_config(config, std::iter::empty());\n        Arc::new(move || policy.clone())\n    }\n\n    async fn wait_for_ws_sender(&self) -> Result<mpsc::Sender<WsOutbound>> {\n        let deadline = Instant::now() + Duration::from_secs(WECOM_WS_READY_WAIT_SECS);\n\n        loop {\n            if let Some(tx) = self.ws_tx.lock().await.as_ref().cloned() {\n                return Ok(tx);\n            }\n\n            if Instant::now() >= deadline {\n                anyhow::bail!(\"WeCom WebSocket not connected\");\n            }\n\n            tokio::time::sleep(Duration::from_millis(WECOM_WS_READY_POLL_MILLIS)).await;\n        }\n    }\n\n    /// Send a JSON frame through the WebSocket outbound channel.\n    async fn ws_send_frame(&self, frame: Value) -> Result<()> {\n        let tx = self.wait_for_ws_sender().await?;\n        tx.send(WsOutbound::Frame(frame))\n            .await\n            .map_err(|e| anyhow::Error::msg(format!(\"WeCom WS outbound channel closed: {e}\")))\n    }\n\n    async fn ws_send_frame_and_wait_for_response(\n        &self,\n        frame: Value,\n        req_id: &str,","sourceCodeStart":358,"sourceCodeEnd":394,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/wecom_ws.rs#L358-L394","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nchannel.send(msg).await?; // races WS connect at startup\n// after\nlet mut n = 0;\nloop {\n    match channel.send(msg.clone()).await {\n        Ok(_) => break,\n        Err(e) if n < 5 && e.to_string().contains(\"not connected\") => {\n            n += 1; tokio::time::sleep(Duration::from_secs(2)).await;\n        }\n        Err(e) => return Err(e),\n    }\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"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.","preventionTips":["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"],"tags":["wecom","websocket","connection","startup","readiness"],"backgroundTag":"websocket-not-connected","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}