zeroclaw-labs/zeroclaw · error · anyhow::Error
upload file failed after token refresh: status={retry_status
Error message
upload file failed after token refresh: status={retry_status}, body={retry_response} What it means
The file upload path (build_lark_file_upload_form with a file_type such as opus/mp4/pdf) hit a refresh-indicating response, refreshed the tenant token, rebuilt the multipart form, retried — and the retry still looked like an invalid-token response. Same defect class as errors 108/109 but for generic file uploads. Retry status and body are included.
Source
Thrown at crates/zeroclaw-channels/src/lark.rs:2257
}
async fn upload_lark_file(
&self,
token: &mut String,
marker: &LarkResolvedMediaMarker,
file_type: &'static str,
) -> anyhow::Result<String> {
let url = format!("{}/im/v1/files", self.api_base());
let form = build_lark_file_upload_form(marker, file_type).await?;
let (status, response) = self.post_multipart_once(&url, token, form).await?;
let response = if should_refresh_lark_tenant_token(status, &response) {
self.invalidate_token().await;
*token = self.get_tenant_access_token().await?;
let retry_form = build_lark_file_upload_form(marker, file_type).await?;
let (retry_status, retry_response) =
self.post_multipart_once(&url, token, retry_form).await?;
if should_refresh_lark_tenant_token(retry_status, &retry_response) {
anyhow::bail!(
"upload file failed after token refresh: status={retry_status}, body={retry_response}"
);
}
ensure_lark_send_success(retry_status, &retry_response, "upload file")?;
retry_response
} else {
ensure_lark_send_success(status, &response, "upload file")?;
response
};
response
.pointer("/data/file_key")
.or_else(|| response.get("file_key"))
.and_then(|v| v.as_str())
.map(str::to_string)
.ok_or_else(|| anyhow::Error::msg("Lark/Feishu file upload returned no file_key"))
}
View on GitHub (pinned to 88bb9c8533)
Solutions
- Verify app_id/app_secret and restart (see error 108).
- Confirm the file upload scope for the relevant file_type is granted and the app version is published.
- Inspect the retry body code: non-99991663 codes indicate permission or payload issues, not tokens.
- Escalate to backoff-and-cooldown instead of immediate retry.
Defensive patterns
Strategy: retry
Type guard
fn is_repeat_stale_token(err: &anyhow::Error) -> bool {
err.to_string().contains("upload file failed after token refresh")
} Try / catch
if is_repeat_stale_token(&e) {
cooldown(Duration::from_secs(60)).await;
return lark.send(msg).await;
} Prevention
- Defer file sends behind a retry queue with cooldown; files tolerate latency better than replies.
- Confirm file-upload scopes per file_type after permission changes.
- Fall back to a text description of the file when upload keeps failing.
When it happens
Trigger: Rotated app_secret or region-mismatched api_base, or the app missing the file-upload scope so the retry with a fresh token is still rejected.
Common situations: Credential rotation while running, duplicated channel processes, missing/withdrawn file upload permission after a permission audit.
Related errors
- upload image failed after token refresh: status={retry_statu
- send failed after token refresh: status={retry_status}, body
- tenant_access_token request failed: status={status}, body={d
- tenant_access_token failed: {msg}
- bot info request failed after token refresh: status={retry_s
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/737c2c8896b514f6.
Report an issue: GitHub.