zeroclaw-labs/zeroclaw · error · anyhow::Error

Mattermost WebSocket closed during authentication: {reason}

Error message

Mattermost WebSocket closed during authentication: {reason}

What it means

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.

Source

Thrown at crates/zeroclaw-channels/src/mattermost.rs:461

                _ = tokio::time::sleep_until(deadline) => {
                    bail!("Mattermost WebSocket authentication handshake timed out");
                }
                frame = read.next() => {
                    let text = match frame {
                        Some(Ok(WsMessage::Text(text))) => text,
                        Some(Ok(WsMessage::Ping(payload))) => {
                            write
                                .send(WsMessage::Pong(payload))
                                .await
                                .context("Mattermost WebSocket handshake pong failed")?;
                            continue;
                        }
                        Some(Ok(WsMessage::Close(frame))) => {
                            let reason = frame
                                .as_ref()
                                .map(|frame| frame.reason.as_ref())
                                .unwrap_or("");
                            bail!("Mattermost WebSocket closed during authentication: {reason}");
                        }
                        Some(Err(error)) => {
                            return Err(error).context("Mattermost WebSocket handshake read failed");
                        }
                        None => bail!("Mattermost WebSocket ended during authentication"),
                        Some(Ok(_)) => continue,
                    };

                    let event: serde_json::Value = serde_json::from_str(text.as_ref())
                        .context("Mattermost WebSocket handshake returned invalid JSON")?;

                    if let Some(ok) = Self::ws_auth_response(&event, auth_seq) {
                        if !ok {
                            bail!("Mattermost WebSocket authentication was rejected");
                        }
                        authenticated = true;
                    }

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Read the close reason in the message — it usually names the cause
  2. Retry once; closes during server deploys are common and self-healing
  3. Verify `base_url` and that `<server>/api/v4/websocket` is the endpoint being hit
  4. Check Mattermost server logs at the close timestamp
Defensive patterns

Strategy: retry

Try / catch

match mm_channel.listen(tx).await {
    Err(e) if e.to_string().contains("closed during authentication") => {
        // server-initiated close mid-handshake: reconnect with backoff;
        // if the reason mentions auth, refresh the token instead
    }
    other => other,
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/ce49d1e4e4062f26. Report an issue: GitHub.