{"record":{"id":"b52b1b3e64e77b2d","repo":"zeroclaw-labs/zeroclaw","slug":"tenant-access-token-request-failed-status-status","errorCode":null,"errorMessage":"tenant_access_token request failed: status={status}, body={data}","messagePattern":"tenant_access_token request failed: status=(.+?), body=(.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"critical","filePath":"crates/zeroclaw-channels/src/lark.rs","lineNumber":1497,"sourceCode":"            if let Some(ref token) = *cached\n                && Instant::now() < token.refresh_after\n            {\n                return Ok(token.value.clone());\n            }\n        }\n\n        let url = self.tenant_access_token_url();\n        let body = serde_json::json!({\n            \"app_id\": self.app_id,\n            \"app_secret\": self.app_secret,\n        });\n\n        let resp = self.http_client().post(&url).json(&body).send().await?;\n        let status = resp.status();\n        let data: serde_json::Value = resp.json().await?;\n\n        if !status.is_success() {\n            anyhow::bail!(\"tenant_access_token request failed: status={status}, body={data}\");\n        }\n\n        let code = data.get(\"code\").and_then(|c| c.as_i64()).unwrap_or(-1);\n        if code != 0 {\n            let msg = data\n                .get(\"msg\")\n                .and_then(|m| m.as_str())\n                .unwrap_or(\"unknown error\");\n            anyhow::bail!(\"tenant_access_token failed: {msg}\");\n        }\n\n        let token = data\n            .get(\"tenant_access_token\")\n            .and_then(|t| t.as_str())\n            .ok_or_else(|| {\n                ::zeroclaw_log::record!(\n                    WARN,\n                    ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)","sourceCodeStart":1479,"sourceCodeEnd":1515,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/lark.rs#L1479-L1515","documentation":"Thrown by LarkChannel::get_tenant_access_token when the POST to {api_base}/auth/v3/tenant_access_token/internal returns a non-2xx HTTP status. This is the first authentication step for the Lark (Feishu/Lark Suite) channel: every send, upload, and audio fetch depends on the tenant_access_token it returns. The full upstream response body is embedded in the message, so the real reason is visible. Transport-level failures (DNS, TLS, connect) are NOT this error; they surface earlier as reqwest::Error via the `?` on send().","triggerScenarios":"POSTing app_id/app_secret to /auth/v3/tenant_access_token/internal and getting 4xx/5xx: 404 from a wrong api_base domain (open.feishu.cn vs open.larksuite.com region mismatch), 400 from a rewritten request path, 401/403 from an egress proxy, or 5xx during a Lark open-platform incident.","commonSituations":"api_base configured for the wrong region (Feishu China vs Lark Suite international), corporate proxies or TLS-inspecting middleboxes mangling the request, Lark-side outages, or a typo'd custom api_base in [channels.lark.<alias>].","solutions":["Read the body=... part of the message; Lark's JSON body states the upstream reason.","Verify api_base matches the app's region: open.feishu.cn for Feishu, open.larksuite.com for Lark Suite.","Reproduce manually: curl -X POST <api_base>/auth/v3/tenant_access_token/internal -H 'Content-Type: application/json' -d '{\"app_id\":\"...\",\"app_secret\":\"...\"}' and compare.","Check HTTP(S)_PROXY / HTTPS_PROXY env vars and firewall egress rules for the channel process.","If status is 5xx, check the Lark/Feishu status page and retry later."],"exampleFix":"// before (config, app created on Feishu China)\n[channels.lark.main]\napi_base = \"https://open.larksuite.com\"\n\n// after\n[channels.lark.main]\napi_base = \"https://open.feishu.cn\"","handlingStrategy":"try-catch","validationCode":"// Pre-flight the token endpoint before wiring the channel into message flow\nasync fn lark_token_preflight(client: &reqwest::Client, api_base: &str, app_id: &str, app_secret: &str) -> anyhow::Result<()> {\n    let url = format!(\"{api_base}/auth/v3/tenant_access_token/internal\");\n    let resp = client.post(&url).json(&serde_json::json!({\"app_id\": app_id, \"app_secret\": app_secret})).send().await?;\n    anyhow::ensure!(resp.status().is_success(), \"token preflight failed: {}\", resp.status());\n    Ok(())\n}","typeGuard":null,"tryCatchPattern":"match channel.send(msg).await {\n    Ok(_) => {}\n    Err(e) if e.to_string().contains(\"tenant_access_token request failed\") => {\n        let retryable = e.to_string().contains(\"status=5\") || e.to_string().contains(\"status=429\");\n        if retryable { backoff_retry().await } else { mark_channel_unhealthy(&e) }\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Validate api_base region (feishu.cn vs larksuite.com) once at startup with a token preflight.","Alert on 4xx vs 5xx token-fetch failures separately: 4xx is config, 5xx is upstream.","Keep credentials and api_base in one canonical config section; avoid per-deploy overrides."],"tags":["lark","feishu","authentication","http","tenant-access-token"],"backgroundTag":"api-token-request-failed","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}