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

  1. Branch on status: 403 -> enable Robot capability / grant scopes and republish; 404 -> fix api_base; 429 -> add backoff; 5xx -> retry later.
  2. Verify the app in the Lark console has bot capability and the scope needed by bot/v3/info.
  3. 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

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


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