{"record":{"id":"083db86ecd5a19b7","repo":"Zackriya-Solutions/meetily","slug":"vad-task-panicked-083db8","errorCode":null,"errorMessage":"VAD task panicked: {}","messagePattern":"VAD task panicked: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"frontend/src-tauri/src/audio/retranscription.rs","lineNumber":262,"sourceCode":"            VAD_REDEMPTION_TIME_MS,\n            |vad_progress, segments_found| {\n                // Map VAD progress (0-100) to overall progress (20-25)\n                let overall_progress = 20 + (vad_progress as f32 * 0.05) as u32;\n                emit_progress(\n                    &app_for_vad,\n                    &meeting_id_for_vad,\n                    \"vad\",\n                    overall_progress,\n                    &format!(\"Detecting speech segments... {}% ({} found)\", vad_progress, segments_found),\n                );\n\n                // Return false to cancel if cancellation requested\n                !RETRANSCRIPTION_CANCELLED.load(Ordering::SeqCst)\n            },\n        )\n    })\n    .await\n    .map_err(|e| anyhow!(\"VAD task panicked: {}\", e))?\n    .map_err(|e| anyhow!(\"VAD processing failed: {}\", e))?;\n\n    let total_segments = speech_segments.len();\n    info!(\"VAD detected {} speech segments (redemption_time={}ms)\", total_segments, VAD_REDEMPTION_TIME_MS);\n\n    // Diagnostic: log segment duration distribution\n    if !speech_segments.is_empty() {\n        let durations_ms: Vec<f64> = speech_segments.iter()\n            .map(|s| s.end_timestamp_ms - s.start_timestamp_ms)\n            .collect();\n        let total_speech_ms: f64 = durations_ms.iter().sum();\n        let avg_duration = total_speech_ms / durations_ms.len() as f64;\n        let min_duration = durations_ms.iter().cloned().fold(f64::INFINITY, f64::min);\n        let max_duration = durations_ms.iter().cloned().fold(f64::NEG_INFINITY, f64::max);\n        info!(\n            \"VAD segment stats: avg={:.0}ms, min={:.0}ms, max={:.0}ms, total_speech={:.1}s/{:.1}s ({:.0}%)\",\n            avg_duration, min_duration, max_duration,\n            total_speech_ms / 1000.0, duration_seconds,","sourceCodeStart":244,"sourceCodeEnd":280,"githubUrl":"https://github.com/Zackriya-Solutions/meetily/blob/0281737d87d26352fb0adc78c8c0975f691b23d1/frontend/src-tauri/src/audio/retranscription.rs#L244-L280","documentation":"The .await on tokio::task::spawn_blocking returned a JoinError, which means the thread that runs get_speech_chunks_with_progress panicked, or the Tokio runtime shut down and aborted the blocking task before it finished. The '{}' holds the panic payload text. This is not the VAD error itself: a VAD failure returns normally and produces 'VAD processing failed' at the second map_err (retranscription.rs:263).","triggerScenarios":"Calling start_retranscription on audio whose decoded samples trigger a panic inside ContinuousVadProcessor::process_audio (for example non-finite f32 samples or an unexpected chunk shape), a bug in the progress callback, or exiting the app while VAD is still scanning a 35+ minute file so the runtime drops the task.","commonSituations":"User quits the app during the 'Detecting speech segments' stage; corrupted or mis-decoded audio containing NaN/inf samples; a VAD refactor that breaks an invariant only on edge-case audio lengths.","solutions":["Re-run with RUST_LOG=debug and read the panic message in '{}' plus the stderr backtrace to find the exact panicking line in audio/vad.rs.","If the panic occurs on app exit, call cancel_retranscription and wait until is_retranscription_in_progress() returns false before shutting the runtime down.","Sanitize decoded samples before VAD: replace NaN/inf f32 values with 0.0 right after decoded.to_whisper_format().","Fix the panicking invariant in the VAD processor (or wrap its body in catch_unwind and convert the panic into a Result error)."],"exampleFix":"// before\nlet audio_samples = tokio::task::spawn_blocking(move || {\n    decoded.to_whisper_format()\n}).await?;\n\n// after: reject non-finite samples before they reach VAD\nlet audio_samples = tokio::task::spawn_blocking(move || {\n    let mut s = decoded.to_whisper_format()?;\n    if s.iter().any(|x| !x.is_finite()) {\n        s.iter_mut().for_each(|x| {\n            if !x.is_finite() { *x = 0.0; }\n        });\n    }\n    Ok(s)\n}).await??;","handlingStrategy":"try-catch","validationCode":"// Rust: reject non-finite samples before handing audio to VAD\nfn samples_are_finite(samples: &[f32]) -> bool {\n    samples.iter().all(|x| x.is_finite())\n}\n// call before spawn_blocking:\n// assert_or_bail!(samples_are_finite(&audio_samples), \"audio contains non-finite samples\");","typeGuard":null,"tryCatchPattern":"// Distinguish JoinError (panic/abort) from the VAD Result\nmatch tokio::task::spawn_blocking(move || get_speech_chunks_with_progress(...)).await {\n    Ok(Ok(segments)) => { /* continue */ }\n    Ok(Err(vad_err)) => log::warn!(\"VAD failed: {vad_err}\"),\n    Err(join_err) if join_err.is_panic() => log::error!(\"VAD panicked: {join_err}\"),\n    Err(_) => log::error!(\"VAD task aborted (runtime shutdown)\"),\n}","preventionTips":["Never exit the app while retranscription is in progress; cancel and await is_retranscription_in_progress() == false first.","Sanitize decoded audio (drop NaN/inf samples) before any DSP stage.","Run large-file VAD under RUST_LOG=debug during development to catch panicking edge cases early."],"tags":["rust","tokio","spawn-blocking","vad","panic","audio"],"backgroundTag":"tokio-join-error","analyzedSha":"0281737d87d26352fb0adc78c8c0975f691b23d1","analyzedAt":"2026-08-16T20:57:52.567Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}