{"record":{"id":"2229203d385be5d9","repo":"zeroclaw-labs/zeroclaw","slug":"initial-ping-failed","errorCode":null,"errorMessage":"initial ping failed","messagePattern":"initial ping failed","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/lark.rs","lineNumber":1161,"sourceCode":"        // starts responding with pongs and we can calibrate the ping_interval.\n        seq = seq.wrapping_add(1);\n        let initial_ping = PbFrame {\n            seq_id: seq,\n            log_id: 0,\n            service: service_id,\n            method: 0,\n            headers: vec![PbHeader {\n                key: \"type\".into(),\n                value: \"ping\".into(),\n            }],\n            payload: None,\n        };\n        if write\n            .send(WsMsg::Binary(initial_ping.encode_to_vec().into()))\n            .await\n            .is_err()\n        {\n            anyhow::bail!(\"initial ping failed\");\n        }\n        // message_id → (fragment_slots, created_at) for multi-part reassembly\n        type FragEntry = (Vec<Option<Vec<u8>>>, Instant);\n        let mut frag_cache: HashMap<String, FragEntry> = HashMap::new();\n\n        loop {\n            tokio::select! {\n                biased;\n\n                _ = hb_interval.tick() => {\n                    seq = seq.wrapping_add(1);\n                    let ping = PbFrame {\n                        seq_id: seq, log_id: 0, service: service_id, method: 0,\n                        headers: vec![PbHeader { key: \"type\".into(), value: \"ping\".into() }],\n                        payload: None,\n                    };\n                    if write.send(WsMsg::Binary(ping.encode_to_vec().into())).await.is_err() {\n                        ::zeroclaw_log::record!(WARN, ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Note).with_outcome(::zeroclaw_log::EventOutcome::Unknown), \"ping failed, reconnecting\");","sourceCodeStart":1143,"sourceCodeEnd":1179,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/lark.rs#L1143-L1179","documentation":"Immediately after the WebSocket handshake in listen_ws, the client sends a protobuf 'ping' frame (the same frame the official SDK sends first so the server starts ponging and the ping_interval can be calibrated). If write.send on the split sink errors on that very first frame, the connection is already dead — the server (or an intermediary proxy) closed the stream between handshake completion and the first write — and the loop bails before entering the event loop.","triggerScenarios":"get_ws_endpoint succeeded and ws_connect_with_proxy completed the TLS/WS handshake, but the write half is closed when sending the initial PbFrame ping: server rejected the connection post-handshake (bad service_id parsed from the wss URL), a proxy (self.proxy_url) tore the tunnel down, or the endpoint URL expired between provisioning and connect.","commonSituations":"Corporate proxy or self.proxy_url misconfiguration killing fresh WS tunnels; reconnecting with a stale wss URL after a network change; clock skew or token expiry invalidating the signed connection.","solutions":["Retry the whole listen cycle — a new get_ws_endpoint call provisions a fresh wss URL, which fixes expired-URL cases","If it fails repeatedly, verify proxy settings for channel.lark (ws_connect_with_proxy uses self.proxy_url) — try without the proxy or with a CONNECT-tunneling-capable one","Log the wss URL's service_id (already logged at connect) and confirm it is non-zero — a 0 service_id means the URL querystring was malformed","Check the app's long-connection mode and credentials, since servers sometimes accept the handshake then drop unauthorized clients"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"loop {\n    if let Err(e) = lark_channel.listen(tx.clone()).await {\n        if e.to_string().contains(\"initial ping failed\") || e.to_string().contains(\"WS endpoint\") {\n            tokio::time::sleep(backoff.next()).await; // full retry provisions a fresh wss URL\n            continue;\n        }\n        return Err(e);\n    }\n}","preventionTips":["Treat first-ping failure as a stale/dead connection: retry the full endpoint-provision cycle rather than the frame","Verify proxy_url supports WebSocket CONNECT tunneling before routing channel.lark through it","Watch the logged service_id — a zero value means the wss URL was malformed and provisioning output should be inspected"],"tags":["lark","websocket","ping","reconnect","proxy"],"backgroundTag":"websocket-send-failed","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}