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

matrix: whoami response did not include device_id; configure

Error message

matrix: whoami response did not include device_id; configure channels.matrix.device-id for access-token login

What it means

Access-token login needs to know which device the token represents (verification keys and E2EE depend on it). The channel reads device_id from the whoami response, but some homeservers omit it. If whoami provides no device_id and the operator did not configure channels.matrix.device-id, login cannot determine the device and aborts, asking for an explicit device-id.

Source

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

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

        Ok(AccessTokenIdentity {
            user_id: configured_user_id.unwrap_or(whoami.user_id),
            device_id: configured_device_id.or(whoami.device_id),
        })
    }

    async fn fetch_access_token_whoami(config: &MatrixConfig) -> Result<WhoamiResponse> {
        let access_token = config
            .access_token
            .as_deref()
            .context("matrix: whoami requires access_token")?;
        let url = matrix_client_api_url(&config.homeserver, WHOAMI_ENDPOINT)?;
        let response = reqwest::Client::builder()
            .timeout(WHOAMI_TIMEOUT)

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Set channels.matrix.device-id to the device the token was created with - visible in the client's active sessions/devices list.
  2. If unknown, do a one-time user-id+password login so a fresh device and session are persisted, then keep that setup or copy the new device id into config.
  3. Prefer minting tokens via a standard client login so they carry a device id.

Example fix

# before
[channels.matrix]
access-token = "syt_..."

# after
[channels.matrix]
access-token = "syt_..."
device-id = "ABCD123EFG"
Defensive patterns

Strategy: validation

Validate before calling

async fn whoami_device_known(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?;
    let server_knows = who["device_id"].as_str().is_some_and(|d| !d.trim().is_empty());
    let configured = cfg.device_id.as_deref().is_some_and(|d| !d.trim().is_empty());
    Ok(server_knows || configured)
}

Prevention

When it happens

Trigger: Access-token login against a homeserver whose whoami response omits device_id, with channels.matrix.device-id unset in config.

Common situations: Homeserver versions or forks that leave device_id out of whoami; tokens minted through unusual flows (appservice, legacy admin) with no bound device; older Synapse-compatible servers.

Related errors


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