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

matrix: whoami response did not include user_id

Error message

matrix: whoami response did not include user_id

What it means

The homeserver answered whoami with HTTP success, but the parsed JSON carried no usable user_id - the field was absent, null, or whitespace-only after trimming. Without a user_id the token's identity is unknown, so access-token login aborts. This points at a nonstandard or degraded homeserver response (or a URL that returns 200 for everything) rather than a ZeroClaw config error.

Source

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

            .get(url)
            .bearer_auth(access_token)
            .send()
            .await
            .context("matrix: whoami request failed")?;
        let status = response.status();

        if !status.is_success() {
            let body = read_whoami_error_body_preview(response).await;
            bail!("matrix: whoami request failed with HTTP {status}: {body}");
        }

        let mut whoami = response
            .json::<WhoamiResponse>()
            .await
            .context("matrix: failed to parse whoami response")?;
        whoami.user_id = whoami.user_id.trim().to_string();
        if whoami.user_id.is_empty() {
            bail!("matrix: whoami response did not include user_id");
        }
        whoami.device_id = whoami
            .device_id
            .map(|device_id| device_id.trim().to_string())
            .filter(|device_id| !device_id.is_empty());

        Ok(whoami)
    }

    async fn read_whoami_error_body_preview(mut response: reqwest::Response) -> String {
        let mut preview = Vec::new();
        let mut truncated = false;

        while preview.len() < WHOAMI_ERROR_BODY_PREVIEW_BYTES {
            let chunk = match response.chunk().await {
                Ok(Some(chunk)) => chunk,
                Ok(None) => break,
                Err(err) => return format!("failed to read response body: {err}"),

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Inspect the raw response from the deployment host: curl -H 'Authorization: Bearer ...' https://HOST/_matrix/client/v3/account/whoami and confirm it returns a user_id field.
  2. If the URL returns something unexpected, fix channels.matrix.homeserver to the real client API base.
  3. If the server genuinely omits user_id, switch to user-id+password login (the whoami check belongs to the token path) or upgrade/fix the homeserver.
Defensive patterns

Strategy: try-catch

Validate before calling

async fn whoami_body_valid(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().is_some_and(|u| !u.trim().is_empty()))
}

Try / catch

match start_matrix(config).await {
    Err(err) if format!("{err:#}").contains("did not include user_id") => {
        // not retryable: dump the homeserver's raw whoami body and compare
        // against a known-good response; this is a server-shape problem
        diagnostic_whoami_dump(&config).await;
        return Err(err);
    }
    other => other,
}

Prevention

When it happens

Trigger: Whoami returns 200 with a body lacking user_id: a nonstandard or forked homeserver, an intercepting proxy returning an empty JSON object, or channels.matrix.homeserver pointing at a service that is not actually a Matrix client API.

Common situations: Custom homeserver implementations; middleware that rewrites responses; wrong DNS/URL landing on a generic web server that returns 200; API-version mismatches where the endpoint exists but responds oddly.

Related errors


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