zeroclaw-labs/zeroclaw · error · anyhow::Error
bot info request failed after token refresh: status={retry_s
Error message
bot info request failed after token refresh: status={retry_status}, body={retry_body} What it means
During bot info discovery (GET bot/v3/info), the first attempt looked like a stale token (HTTP 401 or Lark code 99991663), so the channel invalidated its cached token, fetched a fresh tenant_access_token, and retried — and the retry still returned non-2xx. A failure immediately after a successful token refresh usually means the problem is not token expiry: the request itself is being rejected at the HTTP layer. The retry status and body are included.
Source
Thrown at crates/zeroclaw-channels/src/lark.rs:1845
.await?;
let status = resp.status();
let body = resp
.json::<serde_json::Value>()
.await
.unwrap_or_else(|_| serde_json::json!({}));
Ok((status, body))
}
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")View on GitHub (pinned to 88bb9c8533)
Solutions
- Read retry_status and retry_body in the message for the exact upstream rejection.
- Confirm the app has the Robot (bot) capability enabled and required scopes granted, then re-publish the app version.
- Verify api_base is consistent for both token and API calls and matches the app's region.
- If retry_status is 5xx or 429, wait and retry later; check the Lark status page.
Defensive patterns
Strategy: fallback
Type guard
fn is_post_refresh_failure(err: &anyhow::Error) -> bool {
let s = err.to_string();
s.contains("after token refresh")
} Try / catch
if is_post_refresh_failure(&e) {
// A refresh was already attempted and failed: degrade instead of looping
channel.set_degraded("lark auth unstable").await;
queue_for_later_retry(msg);
} Prevention
- Never wrap post-refresh failures in another refresh loop; one refresh already happened.
- Keep api_base region-consistent for token and API endpoints.
- Subscribe to Lark/Feishu status notifications for incident-driven double failures.
When it happens
Trigger: A freshly minted token still fails bot/v3/info: api_base routes the token endpoint and the API call to different/mismatched regions, a proxy rejects the bot info path, the app lacks bot capability so the endpoint is unavailable, or a Lark 5xx incident straddles both attempts.
Common situations: Mixed-region configuration (token works on one domain, bot info blocked on another), permission or capability changes on the app mid-run, corporate proxies allowing some paths but not others, or an ongoing Lark open-platform incident.
Related errors
- bot info request failed: status={status}, body={body}
- send failed after token refresh: status={retry_status}, body
- tenant_access_token request failed: status={status}, body={d
- bot info failed: code={code}, body={body}
- audio download failed after token refresh: {}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/ebdea72c61018b53.
Report an issue: GitHub.