zeroclaw-labs/zeroclaw · error · anyhow::Error
Mattermost WebSocket stream ended
Error message
Mattermost WebSocket stream ended
What it means
While listening, the WebSocket frame stream returned `None` — the connection ended without a Close frame. The listener surfaces this as an error so the caller (usually the runtime's supervisor loop) can reconnect. It is the classic silent connection drop during an established session.
Source
Thrown at crates/zeroclaw-channels/src/mattermost.rs:1059
}
_ = ping_interval.tick() => {
let ping = serde_json::json!({"seq": seq, "action": "ping"});
write
.send(WsMessage::Text(ping.to_string().into()))
.await
.context("Mattermost WebSocket ping send failed")?;
seq = seq.wrapping_add(1);
}
frame = read.next() => {
let frame = match frame {
Some(Ok(frame)) => {
last_frame = tokio::time::Instant::now();
frame
}
Some(Err(error)) => {
return Err(error).context("Mattermost WebSocket read failed");
}
None => bail!("Mattermost WebSocket stream ended"),
};
let text = match frame {
WsMessage::Text(text) => text,
WsMessage::Ping(payload) => {
write
.send(WsMessage::Pong(payload))
.await
.context("Mattermost WebSocket pong send failed")?;
continue;
}
WsMessage::Close(frame) => {
let reason = frame
.as_ref()
.map(|frame| frame.reason.as_ref())
.unwrap_or("");
bail!("Mattermost WebSocket closed: {reason}");
}View on GitHub (pinned to 88bb9c8533)
Solutions
- Wrap `listen()` in a reconnect loop with exponential backoff — the runtime normally restarts channel listeners on error
- Raise proxy/LB WebSocket idle timeout above Mattermost's ~30s ping interval
- Check Mattermost logs for restarts matching the drop time
Defensive patterns
Strategy: retry
Try / catch
loop {
if let Err(e) = mm_channel.listen(tx.clone()).await {
let transient = e.to_string().contains("stream ended")
|| e.to_string().contains("WebSocket closed");
if !transient {
return Err(e);
}
tokio::time::sleep(backoff.next()).await;
}
} Prevention
- Run all channel listeners under a supervisor that reconnects with exponential backoff
- Set proxy/LB idle timeouts above the Mattermost ping interval (~30s)
- Correlate drop times with server deploy logs before debugging client code
When it happens
Trigger: During an active `listen_websocket` session: network drop, Mattermost restart, or a proxy/LB killing the socket without a close handshake.
Common situations: Long-running bots on flaky networks; server deploys; load-balancer idle timeouts shorter than the server's ping interval.
Related errors
- Mattermost WebSocket ended during authentication
- Mattermost WebSocket authentication handshake timed out
- Mattermost WebSocket closed during authentication: {reason}
- Mattermost WebSocket authentication was rejected
- Mattermost WebSocket closed: {reason}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/2664ada154c54558.
Report an issue: GitHub.