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

Mattermost WebSocket authentication was rejected

Error message

Mattermost WebSocket authentication was rejected

What it means

The Mattermost WebSocket handshake completed, and the server explicitly answered the auth challenge (`ws_auth_response` matched the auth sequence) with a rejection. This is a credential refusal over WebSocket — almost always an invalid, expired, or revoked token.

Source

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

                            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;
                    }

                    if event.get("event").and_then(|value| value.as_str()) == Some("hello") {
                        server_version = Some(
                            event
                                .get("data")
                                .and_then(|data| data.get("server_version"))
                                .and_then(|value| value.as_str())
                                .unwrap_or("unknown")
                                .to_string(),
                        );
                    }
                }
            }
        }
    }

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Regenerate the personal access token and update `bot_token` in config
  2. If using login_id/password, verify those credentials via REST login first
  3. Sanity-check the token: `curl -H "Authorization: Bearer <token>" <server>/api/v4/users/me` must return the bot user
Defensive patterns

Strategy: try-catch

Validate before calling

let resp = client
    .get(format!("{base_url}/api/v4/users/me"))
    .bearer_auth(&token)
    .send()
    .await?;
if !resp.status().is_success() {
    return Err(anyhow::anyhow!("token rejected; refresh before starting WS listen"));
}

Try / catch

match mm_channel.listen(tx).await {
    Err(e) if e.to_string().contains("authentication was rejected") => {
        // fatal credential problem: refresh bot_token / re-login, then reconnect
    }
    other => other,
}

Prevention

When it happens

Trigger: `listen_websocket` authenticating with a bot_token or password-derived session token that the server refuses: revoked personal access token, expired login session, or a token from a different instance.

Common situations: Personal access token regenerated in Mattermost but config still holds the old one; session token invalidated by logout/password change; token copied with extra characters.

Understand the failure class

Related errors


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