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

matrix: configured channels.matrix.user-id ({configured}) do

Error message

matrix: configured channels.matrix.user-id ({configured}) does not match Matrix whoami user_id ({})

What it means

For access-token login the channel calls the homeserver's whoami endpoint to learn which user the token actually authenticates, then cross-checks it against the configured channels.matrix.user-id. A mismatch means the token belongs to a different Matrix account than declared - all sends would go out as the wrong user - so login aborts before the bot joins any room.

Source

Thrown at crates/zeroclaw-channels/src/matrix.rs:1633

    ) -> Result<AccessTokenIdentity> {
        let configured_user_id = non_empty_config_value(config.user_id.as_deref());
        let configured_device_id = non_empty_config_value(config.device_id.as_deref());

        if let (Some(user_id), Some(device_id)) =
            (configured_user_id.as_ref(), configured_device_id.as_ref())
        {
            return Ok(AccessTokenIdentity {
                user_id: user_id.clone(),
                device_id: Some(device_id.clone()),
            });
        }

        let whoami = fetch_access_token_whoami(config).await?;

        if let Some(ref configured) = configured_user_id
            && configured != &whoami.user_id
        {
            bail!(
                "matrix: configured channels.matrix.user-id ({configured}) does not match Matrix whoami user_id ({})",
                whoami.user_id
            );
        }

        if let (Some(configured), Some(actual)) = (&configured_device_id, &whoami.device_id)
            && configured != actual
        {
            bail!(
                "matrix: configured channels.matrix.device-id ({configured}) does not match Matrix whoami device_id ({actual})"
            );
        }

        if configured_device_id.is_none() && whoami.device_id.is_none() {
            bail!(
                "matrix: whoami response did not include device_id; configure channels.matrix.device-id for access-token login"
            );
        }

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Ask the homeserver who the token belongs to (whoami) and correct channels.matrix.user-id to that exact id.
  2. Or mint a new access token for the account named in channels.matrix.user-id and replace the token.
  3. If pinning the user-id is optional for you, remove it and let login adopt the whoami identity.
  4. Check the homeserver part of the user-id - @name:server must match the account's home server exactly.

Example fix

# before: token actually belongs to @ops:example.org
[channels.matrix]
user-id = "@bot:example.org"
access-token = "syt_..."

# after
[channels.matrix]
user-id = "@ops:example.org"
access-token = "syt_..."
Defensive patterns

Strategy: validation

Validate before calling

async fn token_identity_matches(cfg: &MatrixConfig) -> anyhow::Result<bool> {
    let url = format!(
        "{}/_matrix/client/v3/account/whoami",
        cfg.homeserver.trim_end_matches('/')
    );
    let who: serde_json::Value = reqwest::Client::new()
        .get(url)
        .bearer_auth(cfg.access_token.as_deref().unwrap_or_default())
        .send().await?
        .error_for_status()?
        .json().await?;
    Ok(who["user_id"].as_str() == cfg.user_id.as_deref())
}

Prevention

When it happens

Trigger: Access-token login where whoami returns a user_id different from channels.matrix.user-id: the token was minted for another account (e.g. @ops:... while config declares @bot:...), or the user-id has a typo in the localpart or homeserver part.

Common situations: Copy-pasting a token from a different bot account; account deleted and re-created so the same localpart maps elsewhere; wrong homeserver suffix in the user-id; staging and production sharing tokens across accounts.

Related errors


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