zeroclaw-labs/zeroclaw · error · anyhow::Error
initial ping failed
Error message
initial ping failed
What it means
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.
Source
Thrown at crates/zeroclaw-channels/src/lark.rs:1161
// starts responding with pongs and we can calibrate the ping_interval.
seq = seq.wrapping_add(1);
let initial_ping = PbFrame {
seq_id: seq,
log_id: 0,
service: service_id,
method: 0,
headers: vec![PbHeader {
key: "type".into(),
value: "ping".into(),
}],
payload: None,
};
if write
.send(WsMsg::Binary(initial_ping.encode_to_vec().into()))
.await
.is_err()
{
anyhow::bail!("initial ping failed");
}
// message_id → (fragment_slots, created_at) for multi-part reassembly
type FragEntry = (Vec<Option<Vec<u8>>>, Instant);
let mut frag_cache: HashMap<String, FragEntry> = HashMap::new();
loop {
tokio::select! {
biased;
_ = hb_interval.tick() => {
seq = seq.wrapping_add(1);
let ping = PbFrame {
seq_id: seq, log_id: 0, service: service_id, method: 0,
headers: vec![PbHeader { key: "type".into(), value: "ping".into() }],
payload: None,
};
if write.send(WsMsg::Binary(ping.encode_to_vec().into())).await.is_err() {
::zeroclaw_log::record!(WARN, ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Note).with_outcome(::zeroclaw_log::EventOutcome::Unknown), "ping failed, reconnecting");View on GitHub (pinned to 88bb9c8533)
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
Defensive patterns
Strategy: retry
Try / catch
loop {
if let Err(e) = lark_channel.listen(tx.clone()).await {
if e.to_string().contains("initial ping failed") || e.to_string().contains("WS endpoint") {
tokio::time::sleep(backoff.next()).await; // full retry provisions a fresh wss URL
continue;
}
return Err(e);
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- WebSocket stream ended
- WS endpoint failed: code={} msg={}
- QQ WebSocket connection closed: invalid session (fresh auth
- QQ WebSocket connection closed: server requested reconnect (
- QQ WebSocket connection closed: close_code={code}, reason="{
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/2229203d385be5d9.
Report an issue: GitHub.