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
- Set channels.matrix.device-id to the device the token was created with - visible in the client's active sessions/devices list.
- 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.
- 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
- Always pin channels.matrix.device-id when using token auth - it removes the dependency on whoami's optional field.
- Smoke-test token auth against your specific homeserver before rollout; response shapes vary.
- Capture the (token, device-id) pair from the login session that minted it.
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
- matrix: configured channels.matrix.device-id ({configured})
- matrix: whoami request failed with HTTP {status}: {body}
- matrix: configured channels.matrix.user-id ({configured}) do
- matrix: whoami response did not include user_id
- matrix: {reason} Cannot auto-recover because channels.matrix
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/9400138c718f62dd.
Report an issue: GitHub.