{"record":{"id":"f1f45c051b0349f6","repo":"zeroclaw-labs/zeroclaw","slug":"failed-to-fetch-bot-info-status-err","errorCode":null,"errorMessage":"failed to fetch bot info ({status}): {err}","messagePattern":"failed to fetch bot info \\((.+?)\\): (.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"critical","filePath":"crates/zeroclaw-channels/src/line.rs","lineNumber":904,"sourceCode":"        };\n        mac.update(body);\n        mac.verify_slice(&sig_bytes).is_ok()\n    }\n\n    /// Fetch bot info from LINE API. Returns `(userId, displayName)`.\n    async fn fetch_bot_info(&self) -> anyhow::Result<BotInfo> {\n        let url = format!(\"{}/v2/bot/info\", self.api_base_url);\n        let resp = self\n            .client\n            .get(&url)\n            .bearer_auth(&self.channel_access_token)\n            .send()\n            .await?;\n\n        if !resp.status().is_success() {\n            let status = resp.status();\n            let err = resp.text().await.unwrap_or_default();\n            anyhow::bail!(\"failed to fetch bot info ({status}): {err}\");\n        }\n\n        resp.json::<BotInfo>().await.map_err(Into::into)\n    }\n\n    #[cfg(test)]\n    pub(crate) fn resolve_recipient(source: &serde_json::Value) -> Option<String> {\n        let source_type = source.get(\"type\").and_then(|t| t.as_str()).unwrap_or(\"\");\n        match source_type {\n            \"group\" => source\n                .get(\"groupId\")\n                .and_then(|v| v.as_str())\n                .map(str::to_string),\n            \"room\" => source\n                .get(\"roomId\")\n                .and_then(|v| v.as_str())\n                .map(str::to_string),\n            _ => source","sourceCodeStart":886,"sourceCodeEnd":922,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/line.rs#L886-L922","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before (wrong credential kind)\n[channels.line.main]\naccess_token = \"0f9c...channel_secret...\"   # channel secret pasted by mistake\n\n# after\n[channels.line.main]\naccess_token = \"eyJhbGciOi...long-term channel access token...\"","handlingStrategy":"validation","validationCode":"// Startup gate: credentials must fetch bot info before the listener accepts events\nlet bot = line.fetch_bot_info().await\n    .context(\"LINE channel access token rejected; re-issue it in the console and update [channels.line.<alias>]\")?;","typeGuard":null,"tryCatchPattern":"match line.listen().await {\n    Err(e) if e.to_string().contains(\"failed to fetch bot info\") => {\n        mark_channel_unhealthy(&e); // credential problem: do not blind-retry\n        return Err(e);\n    }\n    other => other,\n}","preventionTips":["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."],"tags":["line","authentication","bot-info","http","invalid-token"],"backgroundTag":"invalid-api-credentials","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}