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),
            _ => source

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Issue a fresh long-term channel access token in the LINE console and update [channels.line.<alias>], then restart.
  2. Confirm the credential is the Messaging API channel access token, not the channel secret.
  3. Match each env/config value to its alias when running multiple LINE channels.
  4. 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

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


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