{"record":{"id":"9990877eabe8bc73","repo":"zeroclaw-labs/zeroclaw","slug":"upload-image-failed-after-token-refresh-status-r","errorCode":null,"errorMessage":"upload image failed after token refresh: status={retry_status}, body={retry_response}","messagePattern":"upload image failed after token refresh: status=(.+?), body=(.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/lark.rs","lineNumber":2222,"sourceCode":"        Ok((status, parsed))\n    }\n\n    async fn upload_lark_image(\n        &self,\n        token: &mut String,\n        marker: &LarkResolvedMediaMarker,\n    ) -> anyhow::Result<String> {\n        let url = format!(\"{}/im/v1/images\", self.api_base());\n        let form = build_lark_image_upload_form(marker).await?;\n        let (status, response) = self.post_multipart_once(&url, token, form).await?;\n        let response = if should_refresh_lark_tenant_token(status, &response) {\n            self.invalidate_token().await;\n            *token = self.get_tenant_access_token().await?;\n            let retry_form = build_lark_image_upload_form(marker).await?;\n            let (retry_status, retry_response) =\n                self.post_multipart_once(&url, token, retry_form).await?;\n            if should_refresh_lark_tenant_token(retry_status, &retry_response) {\n                anyhow::bail!(\n                    \"upload image failed after token refresh: status={retry_status}, body={retry_response}\"\n                );\n            }\n            ensure_lark_send_success(retry_status, &retry_response, \"upload image\")?;\n            retry_response\n        } else {\n            ensure_lark_send_success(status, &response, \"upload image\")?;\n            response\n        };\n\n        response\n            .pointer(\"/data/image_key\")\n            .or_else(|| response.get(\"image_key\"))\n            .and_then(|v| v.as_str())\n            .map(str::to_string)\n            .ok_or_else(|| anyhow::Error::msg(\"Lark/Feishu image upload returned no image_key\"))\n    }\n","sourceCodeStart":2204,"sourceCodeEnd":2240,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/lark.rs#L2204-L2240","documentation":"The image upload path hit a refresh-indicating result (401/99991663), rebuilt the multipart form, fetched a fresh tenant_access_token, retried post_multipart_once — and the retry again signaled an invalid token. Structurally identical to error 108 but on the image upload endpoint, including a rebuilt form via build_lark_image_upload_form. Retry status and body are included.","triggerScenarios":"Same credential/region defects as 108, plus cases where the freshly fetched token is rejected because the app lacks image-upload permission or the token belongs to a different app than the upload request implies.","commonSituations":"Secret rotation mid-run, mismatched api_base regions, or concurrent channel instances with divergent credentials for one Lark app.","solutions":["Verify app credentials and restart the channel (see error 108 solutions).","Confirm the app has the image upload scope granted and the app version published.","Check the body code in the message; if it is not 99991663, the failure is permission-side, not token-side.","Do not add another refresh loop on top; one refresh already happened and failed."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":"fn is_repeat_stale_token(err: &anyhow::Error) -> bool {\n    err.to_string().contains(\"upload image failed after token refresh\")\n}","tryCatchPattern":"if is_repeat_stale_token(&e) {\n    cooldown(Duration::from_secs(60)).await; // refresh already attempted once\n    return lark.send(msg).await;             // single later retry, not a loop\n}","preventionTips":["Keep one canonical credential source per app; duplicates cause revocation confusion.","Alert when a freshly refreshed token is rejected — that is a config incident, not noise.","Send text fallbacks when image upload stays broken so conversations continue."],"tags":["lark","image-upload","token-refresh","authentication","multipart"],"backgroundTag":"stale-access-token","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}