zeroclaw-labs/zeroclaw · warning · anyhow::Error

audio download failed after token refresh: {}

Error message

audio download failed after token refresh: {}

What it means

While downloading a Lark audio resource, the first attempt signaled an auth problem (401 or code 99991663); the channel invalidated its token, fetched a fresh one, and retried — and the retried download still returned non-2xx. Since the fresh token did not help, the resource itself is usually the problem (expired, deleted, or access-restricted) rather than authentication. Only the retry status is reported.

Source

Thrown at crates/zeroclaw-channels/src/lark.rs:1952

            .await?;

        let status = resp.status();
        if !status.is_success() {
            let body_text = resp.text().await.unwrap_or_default();
            let body: serde_json::Value =
                serde_json::from_str(&body_text).unwrap_or_else(|_| serde_json::json!({}));

            if should_refresh_lark_tenant_token(status, &body) {
                self.invalidate_token().await;
                let token = self.get_tenant_access_token().await?;
                let resp = self
                    .http_client()
                    .get(&url)
                    .header("Authorization", format!("Bearer {token}"))
                    .send()
                    .await?;
                if !resp.status().is_success() {
                    anyhow::bail!(
                        "audio download failed after token refresh: {}",
                        resp.status()
                    );
                }
                let bytes = Self::stream_audio_bytes(resp).await?;
                return Ok((bytes, inferred_audio_filename(file_key)));
            }

            anyhow::bail!("audio download failed: {}", status);
        }
        let bytes = Self::stream_audio_bytes(resp).await?;
        Ok((bytes, inferred_audio_filename(file_key)))
    }

    async fn try_transcribe_audio_message(
        &self,
        message_id: &str,
        content: &str,

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Reduce processing latency so audio is fetched promptly after the webhook event.
  2. Branch on status: 404 -> resource expired, skip gracefully; 403 -> check message-resource read permission on the app.
  3. Confirm the app has the message-resource (im resource) read scope granted and published.
Defensive patterns

Strategy: fallback

Type guard

fn is_lark_audio_download_error(err: &anyhow::Error) -> bool {
    err.to_string().contains("audio download failed")
}

Try / catch

match lark.transcribe_audio(msg).await {
    Ok(text) => use_text(text),
    Err(e) if is_lark_audio_download_error(&e) => {
        // expired/deleted resource with a fresh token: reply without transcription
        continue_without_transcription(msg).await;
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Lark message resources that have expired out of retention, a message deleted by the user before processing, a file with restricted access the bot cannot read, or 404 on an invalid file_key.

Common situations: Queue backlogs mean audio messages are processed long after arrival, past resource retention; users delete messages immediately after sending; retention policy on the tenant shortens resource availability.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/dce07e170df4041a. Report an issue: GitHub.