{"record":{"id":"ce49d1e4e4062f26","repo":"zeroclaw-labs/zeroclaw","slug":"mattermost-websocket-closed-during-authentication","errorCode":null,"errorMessage":"Mattermost WebSocket closed during authentication: {reason}","messagePattern":"Mattermost WebSocket closed during authentication: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/mattermost.rs","lineNumber":461,"sourceCode":"                _ = tokio::time::sleep_until(deadline) => {\n                    bail!(\"Mattermost WebSocket authentication handshake timed out\");\n                }\n                frame = read.next() => {\n                    let text = match frame {\n                        Some(Ok(WsMessage::Text(text))) => text,\n                        Some(Ok(WsMessage::Ping(payload))) => {\n                            write\n                                .send(WsMessage::Pong(payload))\n                                .await\n                                .context(\"Mattermost WebSocket handshake pong failed\")?;\n                            continue;\n                        }\n                        Some(Ok(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 during authentication: {reason}\");\n                        }\n                        Some(Err(error)) => {\n                            return Err(error).context(\"Mattermost WebSocket handshake read failed\");\n                        }\n                        None => bail!(\"Mattermost WebSocket ended during authentication\"),\n                        Some(Ok(_)) => continue,\n                    };\n\n                    let event: serde_json::Value = serde_json::from_str(text.as_ref())\n                        .context(\"Mattermost WebSocket handshake returned invalid JSON\")?;\n\n                    if let Some(ok) = Self::ws_auth_response(&event, auth_seq) {\n                        if !ok {\n                            bail!(\"Mattermost WebSocket authentication was rejected\");\n                        }\n                        authenticated = true;\n                    }\n","sourceCodeStart":443,"sourceCodeEnd":479,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/mattermost.rs#L443-L479","documentation":"While authenticating the Mattermost WebSocket, the server sent a Close frame; its reason string is included in the error. This is the server (or an intermediary) actively ending the connection mid-handshake, as opposed to the deadline timeout.","triggerScenarios":"During `authenticate_websocket`: the server closes the socket after connect — auth policy rejection, wrong WebSocket endpoint/path, Mattermost shutting down, or a proxy tearing down the upgraded connection.","commonSituations":"Misrouted `base_url` so the WS handshake lands on the wrong endpoint; Mattermost restart during connect; proxy or LB killing new WebSocket connections; server-side session invalidation.","solutions":["Read the close reason in the message — it usually names the cause","Retry once; closes during server deploys are common and self-healing","Verify `base_url` and that `<server>/api/v4/websocket` is the endpoint being hit","Check Mattermost server logs at the close timestamp"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"match mm_channel.listen(tx).await {\n    Err(e) if e.to_string().contains(\"closed during authentication\") => {\n        // server-initiated close mid-handshake: reconnect with backoff;\n        // if the reason mentions auth, refresh the token instead\n    }\n    other => other,\n}","preventionTips":["Reconnect with backoff on handshake closes; treat repeated closes as a server/proxy problem","Log and inspect the close reason string before choosing retry vs re-auth","Keep base_url pointed at the WebSocket-capable endpoint"],"tags":["mattermost","websocket","handshake","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"}