zeroclaw-labs/zeroclaw · error · anyhow::Error
bot info failed: code={code}, body={body}
Error message
bot info failed: code={code}, body={body} What it means
bot/v3/info returned HTTP 200 but a non-zero Lark business code in the JSON body. Lark delivers application-level errors (permission denied, app misconfiguration) inside a 200 response, so this is the 'logical' failure twin of error 103. The code and full body are included in the message, which maps directly to Lark's error-code documentation.
Source
Thrown at crates/zeroclaw-channels/src/lark.rs:1859
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());
Ok(bot_open_id)
}
async fn ensure_bot_open_id(&self) {
if !self.mention_only || self.resolved_bot_open_id().is_some() {
return;
}View on GitHub (pinned to 88bb9c8533)
Solutions
- Open the Lark console -> app -> Permissions & Scopes, grant the bot info scope, and re-publish a new app version.
- Ensure the tenant admin has approved the requested scopes.
- Restart the channel after permission changes so a fresh token carrying the new grants is fetched.
- Map the code= value in the message against Lark's error-code reference for the precise cause.
Defensive patterns
Strategy: validation
Validate before calling
// Startup permission probe: bot info must return code 0 before the channel goes live
let body = lark.fetch_bot_info_body().await?;
let code = body.get("code").and_then(|c| c.as_i64()).unwrap_or(-1);
anyhow::ensure!(code == 0, "Lark app missing bot-info permission (code={code}); grant scope and republish"); Try / catch
if let Err(e) = lark.fetch_bot_open_id().await {
if e.to_string().contains("bot info failed: code=") {
// permission-side failure: retrying with a new token will not help
return Err(e.context("grant the bot-info scope in the Lark console and republish"));
}
return Err(e);
} Prevention
- Treat any code!=0 at startup as a hard failure with an actionable message about scopes.
- After changing scopes, always publish a new app version and restart the channel for a fresh token.
- Track granted scopes in the same change that adds new Lark API usage.
When it happens
Trigger: The tenant_access_token is valid but the app lacks the permission scope required by GET /bot/v3/info, the token was issued for a different app than the one serving the request, or the bot has been disabled for the tenant.
Common situations: Bot capability added but scopes not re-granted or the app version not re-published; pending tenant admin approval for requested scopes; custom app bot switched off by an administrator after initially working.
Related errors
- send failed {context}: code={code}, body={body}
- bot info request failed after token refresh: status={retry_s
- bot info request failed: status={status}, body={body}
- channel does not support room invites
- ACP returned unknown permission optionId: {other}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/479a7424f111da84.
Report an issue: GitHub.