{"record":{"id":"bb0a9967964e78aa","repo":"zeroclaw-labs/zeroclaw","slug":"send-failed-context-code-code-body-body","errorCode":null,"errorMessage":"send failed {context}: code={code}, body={body}","messagePattern":"send failed (.+?): code=(.+?), body=(.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/lark.rs","lineNumber":514,"sourceCode":"    let ttl = Duration::from_secs(ttl_seconds.max(1));\n    let refresh_in = ttl\n        .checked_sub(LARK_TOKEN_REFRESH_SKEW)\n        .unwrap_or(Duration::from_secs(1));\n    now + refresh_in\n}\n\nfn ensure_lark_send_success(\n    status: reqwest::StatusCode,\n    body: &serde_json::Value,\n    context: &str,\n) -> anyhow::Result<()> {\n    if !status.is_success() {\n        anyhow::bail!(\"send failed {context}: status={status}, body={body}\");\n    }\n\n    let code = extract_lark_response_code(body).unwrap_or(0);\n    if code != 0 {\n        anyhow::bail!(\"send failed {context}: code={code}, body={body}\");\n    }\n\n    Ok(())\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\nenum LarkOutgoingMediaKind {\n    Image,\n    File { file_type: &'static str },\n}\n\nimpl LarkOutgoingMediaKind {\n    fn from_marker_kind(kind: &str) -> Option<Self> {\n        match kind.trim().to_ascii_uppercase().as_str() {\n            \"IMAGE\" | \"PHOTO\" => Some(Self::Image),\n            \"DOCUMENT\" | \"FILE\" => Some(Self::File {\n                file_type: \"stream\",\n            }),","sourceCodeStart":496,"sourceCodeEnd":532,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/lark.rs#L496-L532","documentation":"The second branch of ensure_lark_send_success: Lark's open API returns HTTP 200 but carries a business status in the JSON body; extract_lark_response_code pulls that code (defaulting 0) and any non-zero code bails with the code and full body. This is Lark's standard envelope (code/msg/data), so an HTTP-level success can still encode 'invalid token' (99991661/99991663), 'no permission' (230001), 'IP not whitelisted' (230002) and similar.","triggerScenarios":"send_json_with_token_refresh, upload_lark_image, upload_lark_file or request_approval_attributed gets a 200 whose body code is non-zero — e.g. tenant_access_token just expired between refresh and call, the app lacks the specific API scope, or the target id (chat_id/open_id) does not exist for that app.","commonSituations":"Token expiry races under load (refresh later in the pipeline); partially granted scopes — app can send text but not upload files; sending to a chat the bot left; environment IP addresses not whitelisted in the Lark console.","solutions":["Look up the numeric code in Lark's error-code docs — the message embeds both code and body, which pinpoints the exact API-level refusal","For token codes (99991661/99991663/99991668), force a tenant_access_token refresh and retry once before surfacing the error","For permission codes, add the missing scope/permission in the Feishu developer console and republish the app version","For 'not found' target codes, verify the chat_id/open_id and that the bot is still in the conversation"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if let Err(e) = upload_lark_image(...).await {\n    let s = e.to_string();\n    if s.contains(\"code=99991663\") || s.contains(\"code=99991661\") {\n        // token expired/invalid: force refresh, retry once\n    } else if s.contains(\"code=230002\") {\n        // whitelist the egress IP in the Lark console — config action, not code\n    } else {\n        return Err(e); // permission/not-found: fix app scopes or target id\n    }\n}","preventionTips":["Map Lark numeric codes to actions: token codes -> refresh+retry once; permission codes -> console fix; target codes -> verify chat/open id","Never judge Lark success by HTTP status alone — always check the body code envelope","Subscribe to token-expiry errors in logs to detect refresh races early"],"tags":["lark","feishu","api-error-code","tenant-token","permissions"],"backgroundTag":"api-business-error-code","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}