{"record":{"id":"a18e2866bf445f0c","repo":"zeroclaw-labs/zeroclaw","slug":"matrix-whoami-request-failed-with-http-status","errorCode":null,"errorMessage":"matrix: whoami request failed with HTTP {status}: {body}","messagePattern":"matrix: whoami request failed with HTTP (.+?): (.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/matrix.rs","lineNumber":1678,"sourceCode":"        let access_token = config\n            .access_token\n            .as_deref()\n            .context(\"matrix: whoami requires access_token\")?;\n        let url = matrix_client_api_url(&config.homeserver, WHOAMI_ENDPOINT)?;\n        let response = reqwest::Client::builder()\n            .timeout(WHOAMI_TIMEOUT)\n            .build()\n            .context(\"matrix: build whoami HTTP client\")?\n            .get(url)\n            .bearer_auth(access_token)\n            .send()\n            .await\n            .context(\"matrix: whoami request failed\")?;\n        let status = response.status();\n\n        if !status.is_success() {\n            let body = read_whoami_error_body_preview(response).await;\n            bail!(\"matrix: whoami request failed with HTTP {status}: {body}\");\n        }\n\n        let mut whoami = response\n            .json::<WhoamiResponse>()\n            .await\n            .context(\"matrix: failed to parse whoami response\")?;\n        whoami.user_id = whoami.user_id.trim().to_string();\n        if whoami.user_id.is_empty() {\n            bail!(\"matrix: whoami response did not include user_id\");\n        }\n        whoami.device_id = whoami\n            .device_id\n            .map(|device_id| device_id.trim().to_string())\n            .filter(|device_id| !device_id.is_empty());\n\n        Ok(whoami)\n    }\n","sourceCodeStart":1660,"sourceCodeEnd":1696,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/matrix.rs#L1660-L1696","documentation":"As part of access-token validation the channel GETs the homeserver's whoami endpoint and requires HTTP success. A non-success status aborts with the status code plus a body preview, which distinguishes the usual causes: 401 with M_UNKNOWN_TOKEN (revoked or expired token), 403 (forbidden), 404 (wrong homeserver base URL or proxy path), 5xx (homeserver down). A failure to even send the request surfaces earlier as the reqwest-context error 'matrix: whoami request failed'.","triggerScenarios":"Access-token login when whoami returns non-2xx: the token was logged out via logout-all or invalidated by a password change (401), the homeserver URL is mispointed or proxied so /_matrix/client/v3/account/whoami 404s, or the homeserver is under maintenance (5xx).","commonSituations":"Revoked access tokens after security events; reverse proxies rewriting or dropping the Matrix client API path; homeserver pointed at a federation port instead of the client API; homeserver upgrade downtime.","solutions":["Read the body preview in the message: M_UNKNOWN_TOKEN means the token is dead - mint a new one and update channels.matrix.access-token.","Verify channels.matrix.homeserver is the client API base (e.g. https://matrix.example.org) and that /_matrix/client/v3/account/whoami is reachable through any proxy.","For 5xx, retry after a delay - the homeserver is temporarily unavailable or upgrading.","If a password change invalidated the token, switch to user-id+password auth to stop token churn."],"exampleFix":"# before: proxy serves the site root, not the client API\n[channels.matrix]\nhomeserver = \"https://example.org\"\n\n# after\n[channels.matrix]\nhomeserver = \"https://matrix.example.org\"","handlingStrategy":"try-catch","validationCode":"async fn whoami_ok(cfg: &MatrixConfig) -> bool {\n    let url = format!(\n        \"{}/_matrix/client/v3/account/whoami\",\n        cfg.homeserver.trim_end_matches('/')\n    );\n    matches!(\n        reqwest::Client::new()\n            .get(url)\n            .bearer_auth(cfg.access_token.as_deref().unwrap_or_default())\n            .send()\n            .await,\n        Ok(resp) if resp.status().is_success()\n    )\n}","typeGuard":null,"tryCatchPattern":"match start_matrix(config).await {\n    Err(err) => {\n        let text = format!(\"{err:#}\");\n        if text.contains(\"whoami request failed with HTTP 401\") {\n            // token revoked: refresh the token, then retry startup once\n        } else if text.contains(\"HTTP 5\") {\n            // homeserver transient: back off and retry startup\n        } else {\n            return Err(err); // config problem: do not retry\n        }\n    }\n    ok => ok,\n}","preventionTips":["Rotate tokens on a schedule before homeserver-side expiry or revocation.","Point homeserver at the client API root and smoke-test whoami from the deployment host through the same proxy chain.","Prefer password auth for bot accounts to avoid manual token lifecycle.","Alert on 401 whoami events - they always need human action."],"tags":["matrix","whoami","http-status","access-token","auth","homeserver"],"backgroundTag":"http-api-error-status","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}