{"record":{"id":"f64258b6cf7bb85b","repo":"zeroclaw-labs/zeroclaw","slug":"audio-download-failed","errorCode":null,"errorMessage":"audio download failed: {}","messagePattern":"audio download failed: (.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"warning","filePath":"crates/zeroclaw-channels/src/lark.rs","lineNumber":1961,"sourceCode":"                self.invalidate_token().await;\n                let token = self.get_tenant_access_token().await?;\n                let resp = self\n                    .http_client()\n                    .get(&url)\n                    .header(\"Authorization\", format!(\"Bearer {token}\"))\n                    .send()\n                    .await?;\n                if !resp.status().is_success() {\n                    anyhow::bail!(\n                        \"audio download failed after token refresh: {}\",\n                        resp.status()\n                    );\n                }\n                let bytes = Self::stream_audio_bytes(resp).await?;\n                return Ok((bytes, inferred_audio_filename(file_key)));\n            }\n\n            anyhow::bail!(\"audio download failed: {}\", status);\n        }\n        let bytes = Self::stream_audio_bytes(resp).await?;\n        Ok((bytes, inferred_audio_filename(file_key)))\n    }\n\n    async fn try_transcribe_audio_message(\n        &self,\n        message_id: &str,\n        content: &str,\n        manager: &super::transcription::TranscriptionManager,\n    ) -> Option<String> {\n        let file_key = serde_json::from_str::<serde_json::Value>(content)\n            .ok()\n            .and_then(|v| {\n                v.get(\"file_key\")\n                    .and_then(|k| k.as_str())\n                    .map(str::to_owned)\n            })?;","sourceCodeStart":1943,"sourceCodeEnd":1979,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/lark.rs#L1943-L1979","documentation":"The initial download of a Lark audio resource (im/v1/messages/{message_id}/resources) returned non-2xx and did not look like a stale-token response, so no refresh was attempted. Only the status is included. This is the no-refresh twin of error 106 and usually indicates the resource identifier or access is wrong, not the token.","triggerScenarios":"404 for an expired or already-deleted message resource, 403 for a resource the bot lacks permission to read, 400 for an invalid file_key, or 5xx during Lark incidents.","commonSituations":"Delayed processing after webhook backlogs, users deleting messages before the bot fetches audio, missing message-resource read scope on the app.","solutions":["Branch on the reported status: 404 -> skip (expired resource); 403 -> grant and publish the message-resource read scope; 400 -> verify file_key handling; 5xx -> retry later.","Process audio events promptly to stay inside Lark's resource retention window.","If this fires for every audio message, test one file_key manually with a fresh token to isolate scope vs expiry."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":"fn is_lark_audio_download_error(err: &anyhow::Error) -> bool {\n    err.to_string().contains(\"audio download failed\")\n}","tryCatchPattern":"if is_lark_audio_download_error(&e) {\n    tracing::warn!(error = %e, \"lark audio unavailable (expired/forbidden); continuing without it\");\n    return Ok(());\n}","preventionTips":["Download resources as soon as the event arrives, not after full pipeline processing.","Separate 'media fetch' failures from 'send' failures in alerting; only the latter block replies.","Verify message-resource scopes before going live."],"tags":["lark","audio","download","http","expired-resource"],"backgroundTag":"http-error-response","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}