Zackriya-Solutions/meetily · error · anyhow::Error

Decode task panicked: {}

Error message

Decode task panicked: {}

What it means

decode_audio_file ran inside tokio::task::spawn_blocking and the task ended in a panic; the JoinError is reported as 'Decode task panicked'. The decoder crashed on its input (an unwrap/index/alloc failure inside the decoding path) rather than returning an Err, so the audio file itself is the prime suspect.

Source

Thrown at frontend/src-tauri/src/audio/retranscription.rs:205

        "Starting retranscription for meeting {} with language {:?}, model {:?}, provider {:?}",
        meeting_id, language, model, provider
    );

    // Emit progress: decoding
    emit_progress(&app, &meeting_id, "decoding", 5, "Decoding audio file...");

    // Check for cancellation
    if RETRANSCRIPTION_CANCELLED.load(Ordering::SeqCst) {
        return Err(anyhow!("Retranscription cancelled"));
    }

    // Decode the audio file (CPU-intensive, run in blocking task)
    let path_for_decode = audio_path.clone();
    let decoded = tokio::task::spawn_blocking(move || {
        decode_audio_file(&path_for_decode)
    })
    .await
    .map_err(|e| anyhow!("Decode task panicked: {}", e))??;
    let duration_seconds = decoded.duration_seconds;

    info!(
        "Decoded audio: {:.2}s, {}Hz, {} channels",
        duration_seconds, decoded.sample_rate, decoded.channels
    );

    emit_progress(&app, &meeting_id, "decoding", 15, "Converting audio format...");

    // Check for cancellation
    if RETRANSCRIPTION_CANCELLED.load(Ordering::SeqCst) {
        return Err(anyhow!("Retranscription cancelled"));
    }

    // Convert to 16kHz mono format (CPU-intensive, run in blocking task)
    let audio_samples = tokio::task::spawn_blocking(move || {
        decoded.to_whisper_format()
    })

View on GitHub (pinned to 0281737d87)

Solutions

  1. Open the file in another player to verify integrity; re-export or discard damaged audio
  2. Log the panic payload (join_error.into_panic() downcast to String/&str) to locate the decoder crash site
  3. Pre-validate the file (header sanity, size vs. declared length) before decode, or wrap decode_audio_file in catch_unwind to convert panics into errors

Example fix

// before
let decoded = tokio::task::spawn_blocking(move || decode_audio_file(&path))
    .await
    .map_err(|e| anyhow!("Decode task panicked: {}", e))??;

// after - convert the panic into an error carrying the payload
let decoded = tokio::task::spawn_blocking(move || {
    std::panic::catch_unwind(|| decode_audio_file(&path))
        .map_err(|p| anyhow!("decode panicked: {:?}", p.downcast_ref::<String>().cloned()))
})
.await
.map_err(|e| anyhow!("Decode task panicked: {}", e))???;
Defensive patterns

Strategy: try-catch

Validate before calling

// Cheap pre-decode sanity: header present and size plausible
fn audio_plausibly_valid(path: &std::path::Path) -> bool {
    let mut buf = [0u8; 12];
    match std::fs::File::open(path) {
        Ok(mut f) => std::io::Read::read_exact(&mut f, &mut buf).is_ok() && buf.len() == 12,
        Err(_) => false,
    }
}

Try / catch

Treat the JoinError distinctly from decode errors: inspect .is_panic() and downcast into_panic() for the message; report 'audio file is corrupt or unsupported' to the user and offer file repair/replacement rather than retry (retrying a panic on the same input will panic again).

Prevention

When it happens

Trigger: Corrupted or truncated audio file - e.g., the app was killed mid-save leaving a WAV/M4A header claiming more data than the file holds; a container/codec edge case that trips an unwrap in the decoder; allocation failure on a pathologically large file.

Common situations: Retranscribing a recording whose save was interrupted by a crash or force-quit; partially copied or synced audio files; exotic files produced by nonstandard recorders.

Related errors


AI-assisted analysis of Zackriya-Solutions/meetily@0281737d87 (2026-08-16). Data as JSON: /api/errors/fd0bd0902e3a7f10. Report an issue: GitHub.