{"record":{"id":"2664ada154c54558","repo":"zeroclaw-labs/zeroclaw","slug":"mattermost-websocket-stream-ended","errorCode":null,"errorMessage":"Mattermost WebSocket stream ended","messagePattern":"Mattermost WebSocket stream ended","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/mattermost.rs","lineNumber":1059,"sourceCode":"                }\n                _ = ping_interval.tick() => {\n                    let ping = serde_json::json!({\"seq\": seq, \"action\": \"ping\"});\n                    write\n                        .send(WsMessage::Text(ping.to_string().into()))\n                        .await\n                        .context(\"Mattermost WebSocket ping send failed\")?;\n                    seq = seq.wrapping_add(1);\n                }\n                frame = read.next() => {\n                    let frame = match frame {\n                        Some(Ok(frame)) => {\n                            last_frame = tokio::time::Instant::now();\n                            frame\n                        }\n                        Some(Err(error)) => {\n                            return Err(error).context(\"Mattermost WebSocket read failed\");\n                        }\n                        None => bail!(\"Mattermost WebSocket stream ended\"),\n                    };\n\n                    let text = match frame {\n                        WsMessage::Text(text) => text,\n                        WsMessage::Ping(payload) => {\n                            write\n                                .send(WsMessage::Pong(payload))\n                                .await\n                                .context(\"Mattermost WebSocket pong send failed\")?;\n                            continue;\n                        }\n                        WsMessage::Close(frame) => {\n                            let reason = frame\n                                .as_ref()\n                                .map(|frame| frame.reason.as_ref())\n                                .unwrap_or(\"\");\n                            bail!(\"Mattermost WebSocket closed: {reason}\");\n                        }","sourceCodeStart":1041,"sourceCodeEnd":1077,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/mattermost.rs#L1041-L1077","documentation":"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.","triggerScenarios":"During an active `listen_websocket` session: network drop, Mattermost restart, or a proxy/LB killing the socket without a close handshake.","commonSituations":"Long-running bots on flaky networks; server deploys; load-balancer idle timeouts shorter than the server's ping interval.","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"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"loop {\n    if let Err(e) = mm_channel.listen(tx.clone()).await {\n        let transient = e.to_string().contains(\"stream ended\")\n            || e.to_string().contains(\"WebSocket closed\");\n        if !transient {\n            return Err(e);\n        }\n        tokio::time::sleep(backoff.next()).await;\n    }\n}","preventionTips":["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"],"tags":["mattermost","websocket","connection-reset","rust"],"backgroundTag":"websocket-connection-closed","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}