zeroclaw-labs/zeroclaw · critical · anyhow::Error
failed to fetch bot info ({status}): {err}
Error message
failed to fetch bot info ({status}): {err} What it means
LINE channel: fetch_bot_info GETs api.line.me/v2/bot/info with the configured channel access token during listen startup and got non-2xx. The status and raw error body are included. This is the channel's credential gate: when it fails, the webhook listener has effectively invalid credentials.
Source
Thrown at crates/zeroclaw-channels/src/line.rs:904
};
mac.update(body);
mac.verify_slice(&sig_bytes).is_ok()
}
/// Fetch bot info from LINE API. Returns `(userId, displayName)`.
async fn fetch_bot_info(&self) -> anyhow::Result<BotInfo> {
let url = format!("{}/v2/bot/info", self.api_base_url);
let resp = self
.client
.get(&url)
.bearer_auth(&self.channel_access_token)
.send()
.await?;
if !resp.status().is_success() {
let status = resp.status();
let err = resp.text().await.unwrap_or_default();
anyhow::bail!("failed to fetch bot info ({status}): {err}");
}
resp.json::<BotInfo>().await.map_err(Into::into)
}
#[cfg(test)]
pub(crate) fn resolve_recipient(source: &serde_json::Value) -> Option<String> {
let source_type = source.get("type").and_then(|t| t.as_str()).unwrap_or("");
match source_type {
"group" => source
.get("groupId")
.and_then(|v| v.as_str())
.map(str::to_string),
"room" => source
.get("roomId")
.and_then(|v| v.as_str())
.map(str::to_string),
_ => sourceView on GitHub (pinned to 88bb9c8533)
Solutions
- Issue a fresh long-term channel access token in the LINE console and update [channels.line.<alias>], then restart.
- Confirm the credential is the Messaging API channel access token, not the channel secret.
- Match each env/config value to its alias when running multiple LINE channels.
- If status is 429/5xx, back off and retry startup.
Example fix
# before (wrong credential kind) [channels.line.main] access_token = "0f9c...channel_secret..." # channel secret pasted by mistake # after [channels.line.main] access_token = "eyJhbGciOi...long-term channel access token..."
Defensive patterns
Strategy: validation
Validate before calling
// Startup gate: credentials must fetch bot info before the listener accepts events
let bot = line.fetch_bot_info().await
.context("LINE channel access token rejected; re-issue it in the console and update [channels.line.<alias>]")?; Try / catch
match line.listen().await {
Err(e) if e.to_string().contains("failed to fetch bot info") => {
mark_channel_unhealthy(&e); // credential problem: do not blind-retry
return Err(e);
}
other => other,
} Prevention
- Run a bot-info pre-flight on boot and on credential rotation.
- After issuing a new long-term token, update config everywhere the old one was used — revocation is immediate.
- Name tokens per alias in config so credentials cannot be cross-wired.
When it happens
Trigger: 401 with an invalid/expired/revoked channel access token (re-issuing a long-term token revokes the previous one); 403 when using the wrong credential type (channel secret instead of access token, or a LINE Login channel's token); 429/5xx under load or incidents.
Common situations: Token re-issued in the LINE console but config not updated; channel_secret pasted where the access token belongs; mixing credentials between multiple LINE channels or aliases; account/plan changes invalidating tokens.
Related errors
- tenant_access_token request failed: status={status}, body={d
- bot info request failed after token refresh: status={retry_s
- bot info request failed: status={status}, body={body}
- audio download failed ({status}) for message {message_id}
- getUploadUrl failed ({status}): {body}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/f1f45c051b0349f6.
Report an issue: GitHub.