zeroclaw-labs/zeroclaw · warning · anyhow::Error
audio download failed ({status}) for message {message_id}
Error message
audio download failed ({status}) for message {message_id} What it means
LINE channel: download_audio_content GETs the message content (Messaging API content endpoint) using the channel access token and the message_id, and the response was non-2xx. The message includes the HTTP status and the specific message_id. Transport errors surface separately as reqwest::Error.
Source
Thrown at crates/zeroclaw-channels/src/line.rs:200
/// Download audio/voice message binary from the LINE Content API.
/// LINE stores message content at `https://api-data.line.me/v2/bot/message/{id}/content`.
/// Audio messages are typically M4A (`audio/x-m4a`).
async fn download_audio_content(
client: &reqwest::Client,
content_api_base_url: &str,
channel_access_token: &str,
message_id: &str,
) -> anyhow::Result<Vec<u8>> {
let url = format!("{content_api_base_url}/v2/bot/message/{message_id}/content");
let resp = client
.get(&url)
.bearer_auth(channel_access_token)
.send()
.await?;
if !resp.status().is_success() {
let status = resp.status();
anyhow::bail!("audio download failed ({status}) for message {message_id}");
}
let mut bytes = Vec::new();
let mut stream = resp;
while let Some(chunk) = stream.chunk().await? {
bytes.extend_from_slice(&chunk);
if bytes.len() as u64 > MAX_LINE_AUDIO_BYTES {
anyhow::bail!(
"audio exceeds {} byte limit for message {message_id}",
MAX_LINE_AUDIO_BYTES
);
}
}
Ok(bytes)
}
fn build_webhook_router(state: Arc<LineState>) -> axum::Router {
use axum::{Router, routing::post};View on GitHub (pinned to 88bb9c8533)
Solutions
- Branch on status: 401 -> re-copy the current long-term channel access token into [channels.line.<alias>]; 404 -> content expired, skip transcription; 429/5xx -> retry with backoff.
- Reduce webhook-to-processing latency so audio is downloaded within the content retention window.
- Confirm one token per channel and that no other process uses a stale token for the same channel.
Defensive patterns
Strategy: fallback
Validate before calling
// Cheap credential probe before downloading content
async fn line_token_ok(client: &reqwest::Client, token: &str) -> bool {
client.get("https://api.line.me/v2/bot/info")
.bearer_auth(token).send().await
.map(|r| r.status().is_success()).unwrap_or(false)
} Type guard
fn is_line_audio_download_error(err: &anyhow::Error) -> bool {
err.to_string().contains("audio download failed")
} Try / catch
match line.download_audio_content(msg_id).await {
Ok(bytes) => transcribe(bytes),
Err(e) if is_line_audio_download_error(&e) => continue_without_transcription(msg).await,
Err(e) => return Err(e),
} Prevention
- Process LINE audio webhooks immediately; content URLs expire.
- After re-issuing a channel access token, update config and restart every consumer.
- Keep transcription optional in the pipeline.
When it happens
Trigger: 401 with a revoked or rotated channel access token, 404 when the message content has passed LINE's retention window or the message_id is invalid, 429/5xx under load or incidents.
Common situations: Webhook backlogs processed late so audio content expired; the channel access token was re-issued in the LINE console (old tokens are revoked immediately); wrong token configured for the alias.
Related errors
- audio download failed: {}
- audio download failed after token refresh: {}
- audio exceeds {} byte limit for message {message_id}
- failed to fetch bot info ({status}): {err}
- attachment download failed ({status}): {body}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/583d363e5316a1c1.
Report an issue: GitHub.