Zackriya-Solutions/meetily · error
Decode task join error: {}
Error message
Decode task join error: {} What it means
Raised when `.await` on the `tokio::task::spawn_blocking` handle for `decode_audio_file_with_progress` returns a JoinError, meaning the blocking closure ended abnormally (panicked inside Symphonia, or was cancelled at tokio runtime shutdown) instead of returning Ok/Err. The double `??` in the source distinguishes two failures: the first `?` is this join/panic failure, the second is a normal decode error returned by the decoder. In practice this error means the decoder panicked mid-file.
Source
Thrown at frontend/src-tauri/src/audio/import.rs:388
return Err(anyhow!("Import cancelled"));
}
emit_progress(&app, "decoding", 15, "Decoding audio file...");
// Decode the audio file with progress updates
let app_for_decode = app.clone();
let decode_progress = Box::new(move |progress: u32, msg: &str| {
// Map decode progress: 15% + (progress * 0.05) to go from 15% to 20%
let overall_progress = 15 + ((progress as f32 * 0.05) as u32);
emit_progress(&app_for_decode, "decoding", overall_progress, msg);
});
let path_for_decode = dest_path.clone();
let decoded = tokio::task::spawn_blocking(move || {
decode_audio_file_with_progress(&path_for_decode, Some(decode_progress))
})
.await
.map_err(|e| anyhow!("Decode task join error: {}", e))??;
let duration_seconds = decoded.duration_seconds;
info!(
"Decoded audio: {:.2}s, {}Hz, {} channels",
duration_seconds, decoded.sample_rate, decoded.channels
);
emit_progress(&app, "resampling", 20, "Converting audio format...");
// Check for cancellation
if IMPORT_CANCELLED.load(Ordering::SeqCst) {
let _ = std::fs::remove_dir_all(&meeting_folder);
return Err(anyhow!("Import cancelled"));
}
// Convert to 16kHz mono format with progress updates
let app_for_resample = app.clone();
let resample_progress = Box::new(move |progress: u32, msg: &str| {View on GitHub (pinned to 0281737d87)
Solutions
- Get the real panic: run with RUST_BACKTRACE=1 and call `JoinError::try_into_panic()` in the map_err branch, logging or resuming the payload — the backtrace names the failing line in decoder.rs
- Verify the file outside the app (`ffprobe file` or `ffmpeg -v error -i file -f null -`) and re-encode it to a clean WAV/MP3 before importing
- Fail earlier: run `validate_audio_file` before starting the import pipeline — its metadata fallback already performs a full decode, so bad files surface as a normal decode error before any folders are created
- If it only happens at app shutdown, branch on `JoinError::is_cancelled()` and block app exit until imports finish
Example fix
// before
let decoded = tokio::task::spawn_blocking(move || {
decode_audio_file_with_progress(&path_for_decode, Some(decode_progress))
})
.await
.map_err(|e| anyhow!("Decode task join error: {}", e))??;
// after — surface the real panic and distinguish cancellation
let decoded = match tokio::task::spawn_blocking(move || {
decode_audio_file_with_progress(&path_for_decode, Some(decode_progress))
}).await {
Ok(r) => r?,
Err(j) if j.is_cancelled() => return Err(anyhow!("Decode cancelled (runtime shutdown)")),
Err(j) => {
if let Ok(payload) = j.try_into_panic() {
error!("decode task panicked");
std::panic::resume_unwind(payload); // real backtrace lands in logs
}
return Err(anyhow!("Decode task join error"));
}
}; Defensive patterns
Strategy: try-catch
Validate before calling
// pre-flight the decode before starting the import pipeline
let info = validate_audio_file(Path::new(&source_path))?; // falls back to full decode on bad metadata
if info.duration_seconds < 0.1 {
return Err(anyhow!("Audio too short to decode reliably"));
} Try / catch
Match the spawn_blocking Result instead of stringifying the JoinError: `Ok(r) => r?, Err(j) if j.is_cancelled() => /* benign at shutdown */, Err(j) => { if let Ok(p) = j.try_into_panic() { log::error!("decode panicked"); std::panic::resume_unwind(p); } /* else surface join error */ }` — never mask the panic payload. Prevention
- Run validate_audio_file (or ffprobe) on every file before the import pipeline starts
- Keep RUST_BACKTRACE=1 in dev builds so join errors reveal their origin
- Re-encode suspicious or renamed files to a canonical format before import
- Treat JoinError::is_cancelled() during app shutdown as benign, not an error
When it happens
Trigger: Importing a file that Symphonia can probe but that makes the decode loop panic (corrupt or truncated stream, container renamed to a misleading extension, unsupported codec path); the decode progress closure panicking while emitting `import-progress`; or the tokio runtime being shut down (app quit) while decoding a large file is still in flight.
Common situations: Importing a recording whose writer crashed (truncated MP3/M4A), a partially downloaded audio file, a file renamed from another format to .mp3/.wav, or test/CI runs where the async runtime is dropped before spawn_blocking completes.
Related errors
- Resample task join error: {}
- Copy task join error: {}
- VAD task panicked: {}
- Decode task panicked: {}
- VAD task panicked: {}
AI-assisted analysis of Zackriya-Solutions/meetily@0281737d87 (2026-08-16).
Data as JSON: /api/errors/03058612b7257df4.
Report an issue: GitHub.