zeroclaw-labs/zeroclaw · error · anyhow::Error
bot info request failed: status={status}, body={body}
Error message
bot info request failed: status={status}, body={body} What it means
The first GET /open-apis/bot/v3/info call with the cached tenant_access_token returned non-2xx, and the response did not indicate a stale token (not HTTP 401, not Lark code 99991663), so no refresh was attempted. The channel surfaces the status and full body. Commonly a permission/capability problem or routing issue rather than authentication expiry.
Source
Thrown at crates/zeroclaw-channels/src/lark.rs:1852
}
async fn refresh_bot_open_id(&self) -> anyhow::Result<Option<String>> {
let token = self.get_tenant_access_token().await?;
let (status, body) = self.fetch_bot_open_id_with_token(&token).await?;
let body = if should_refresh_lark_tenant_token(status, &body) {
self.invalidate_token().await;
let refreshed = self.get_tenant_access_token().await?;
let (retry_status, retry_body) = self.fetch_bot_open_id_with_token(&refreshed).await?;
if !retry_status.is_success() {
anyhow::bail!(
"bot info request failed after token refresh: status={retry_status}, body={retry_body}"
);
}
retry_body
} else {
if !status.is_success() {
anyhow::bail!("bot info request failed: status={status}, body={body}");
}
body
};
let code = body.get("code").and_then(|c| c.as_i64()).unwrap_or(-1);
if code != 0 {
anyhow::bail!("bot info failed: code={code}, body={body}");
}
let bot_open_id = body
.pointer("/bot/open_id")
.or_else(|| body.pointer("/data/bot/open_id"))
.and_then(|v| v.as_str())
.map(str::trim)
.filter(|v| !v.is_empty())
.map(str::to_owned);
self.set_resolved_bot_open_id(bot_open_id.clone());View on GitHub (pinned to 88bb9c8533)
Solutions
- Branch on status: 403 -> enable Robot capability / grant scopes and republish; 404 -> fix api_base; 429 -> add backoff; 5xx -> retry later.
- Verify the app in the Lark console has bot capability and the scope needed by bot/v3/info.
- If a fresh token is suspected anyway, restart the channel to force get_tenant_access_token.
Defensive patterns
Strategy: try-catch
Try / catch
match lark.fetch_bot_open_id().await {
Ok(id) => {}
Err(e) if e.to_string().contains("bot info request failed: status=403") => {
alert("Lark app lacks bot capability/scopes for bot/v3/info");
}
Err(e) if e.to_string().contains("status=429") => backoff_retry().await,
Err(e) => return Err(e),
} Prevention
- Fetch bot info once at channel startup to surface permission problems before traffic.
- Back off on 429; do not hammer bot/v3/info during startup storms.
- Verify app capability and published version after every console-side permission change.
When it happens
Trigger: 403 when the app is not a bot or lacks permissions, 404 when api_base or the path is wrong, 429 from startup bursts hitting rate limits, or 5xx during Lark incidents.
Common situations: Bot capability never enabled on the custom app, scopes granted but the app version not re-published, api_base typo, or many channel instances starting simultaneously and tripping rate limits.
Related errors
- bot info request failed after token refresh: status={retry_s
- tenant_access_token request failed: status={status}, body={d
- bot info failed: code={code}, body={body}
- audio download failed: {}
- Lark add_reaction failed for {message_id}: status={status},
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/7cc28b4c0b641fb5.
Report an issue: GitHub.