{"record":{"id":"5064d7b50633aa28","repo":"zeroclaw-labs/zeroclaw","slug":"mattermost-websocket-closed-reason","errorCode":null,"errorMessage":"Mattermost WebSocket closed: {reason}","messagePattern":"Mattermost WebSocket closed: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/mattermost.rs","lineNumber":1076,"sourceCode":"                        }\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                        }\n                        _ => continue,\n                    };\n\n                    let event: serde_json::Value = match serde_json::from_str(text.as_ref()) {\n                        Ok(event) => event,\n                        Err(error) => {\n                            ::zeroclaw_log::record!(\n                                WARN,\n                                ::zeroclaw_log::Event::new(\n                                    module_path!(),\n                                    ::zeroclaw_log::Action::Note,\n                                )\n                                .with_outcome(::zeroclaw_log::EventOutcome::Unknown)\n                                .with_attrs(::serde_json::json!({\n                                    \"alias\": self.alias,\n                                    \"error\": error.to_string(),\n                                })),","sourceCodeStart":1058,"sourceCodeEnd":1094,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/mattermost.rs#L1058-L1094","documentation":"The server sent a WebSocket Close frame during an established listening session; the reason string is included. This is the normal shutdown path for the protocol: server restarts, session invalidation, or proxy closes. The error exists so the listener can distinguish close-with-reason from silent drops and reconnect.","triggerScenarios":"`listen_websocket` receives `WsMessage::Close` mid-session: Mattermost shutdown/restart, server-side token/session invalidation, or an intermediary closing the connection.","commonSituations":"Server upgrades and nightly restarts; token revoked causing the server to close all its sockets; proxy idle closes.","solutions":["Treat as reconnectable: re-invoke `listen()` with backoff","Inspect the close reason — auth-policy closes need a token refresh, not a retry","If closes repeat rapidly, check server health and token validity"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"match mm_channel.listen(tx).await {\n    Err(e) if e.to_string().contains(\"Mattermost WebSocket closed:\") => {\n        let reason = e.to_string();\n        if reason.contains(\"auth\") {\n            // refresh credentials before reconnecting\n        }\n        // otherwise reconnect with backoff\n    }\n    other => other,\n}","preventionTips":["Treat Close frames as normal lifecycle events: log the reason, reconnect","Only escalate when closes recur faster than a backoff floor","Preserve the close reason in logs — it distinguishes restarts from policy closes"],"tags":["mattermost","websocket","connection-closed","rust"],"backgroundTag":"websocket-connection-closed","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}