{"record":{"id":"479a7424f111da84","repo":"zeroclaw-labs/zeroclaw","slug":"bot-info-failed-code-code-body-body","errorCode":null,"errorMessage":"bot info failed: code={code}, body={body}","messagePattern":"bot info failed: code=(.+?), body=(.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/lark.rs","lineNumber":1859,"sourceCode":"            self.invalidate_token().await;\n            let refreshed = self.get_tenant_access_token().await?;\n            let (retry_status, retry_body) = self.fetch_bot_open_id_with_token(&refreshed).await?;\n            if !retry_status.is_success() {\n                anyhow::bail!(\n                    \"bot info request failed after token refresh: status={retry_status}, body={retry_body}\"\n                );\n            }\n            retry_body\n        } else {\n            if !status.is_success() {\n                anyhow::bail!(\"bot info request failed: status={status}, body={body}\");\n            }\n            body\n        };\n\n        let code = body.get(\"code\").and_then(|c| c.as_i64()).unwrap_or(-1);\n        if code != 0 {\n            anyhow::bail!(\"bot info failed: code={code}, body={body}\");\n        }\n\n        let bot_open_id = body\n            .pointer(\"/bot/open_id\")\n            .or_else(|| body.pointer(\"/data/bot/open_id\"))\n            .and_then(|v| v.as_str())\n            .map(str::trim)\n            .filter(|v| !v.is_empty())\n            .map(str::to_owned);\n\n        self.set_resolved_bot_open_id(bot_open_id.clone());\n        Ok(bot_open_id)\n    }\n\n    async fn ensure_bot_open_id(&self) {\n        if !self.mention_only || self.resolved_bot_open_id().is_some() {\n            return;\n        }","sourceCodeStart":1841,"sourceCodeEnd":1877,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/lark.rs#L1841-L1877","documentation":"bot/v3/info returned HTTP 200 but a non-zero Lark business code in the JSON body. Lark delivers application-level errors (permission denied, app misconfiguration) inside a 200 response, so this is the 'logical' failure twin of error 103. The code and full body are included in the message, which maps directly to Lark's error-code documentation.","triggerScenarios":"The tenant_access_token is valid but the app lacks the permission scope required by GET /bot/v3/info, the token was issued for a different app than the one serving the request, or the bot has been disabled for the tenant.","commonSituations":"Bot capability added but scopes not re-granted or the app version not re-published; pending tenant admin approval for requested scopes; custom app bot switched off by an administrator after initially working.","solutions":["Open the Lark console -> app -> Permissions & Scopes, grant the bot info scope, and re-publish a new app version.","Ensure the tenant admin has approved the requested scopes.","Restart the channel after permission changes so a fresh token carrying the new grants is fetched.","Map the code= value in the message against Lark's error-code reference for the precise cause."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Startup permission probe: bot info must return code 0 before the channel goes live\nlet body = lark.fetch_bot_info_body().await?;\nlet code = body.get(\"code\").and_then(|c| c.as_i64()).unwrap_or(-1);\nanyhow::ensure!(code == 0, \"Lark app missing bot-info permission (code={code}); grant scope and republish\");","typeGuard":null,"tryCatchPattern":"if let Err(e) = lark.fetch_bot_open_id().await {\n    if e.to_string().contains(\"bot info failed: code=\") {\n        // permission-side failure: retrying with a new token will not help\n        return Err(e.context(\"grant the bot-info scope in the Lark console and republish\"));\n    }\n    return Err(e);\n}","preventionTips":["Treat any code!=0 at startup as a hard failure with an actionable message about scopes.","After changing scopes, always publish a new app version and restart the channel for a fresh token.","Track granted scopes in the same change that adds new Lark API usage."],"tags":["lark","bot-info","permissions","scopes","business-error"],"backgroundTag":"missing-api-permission","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}