Zackriya-Solutions/meetily · error
Copy task join error: {}
Error message
Copy task join error: {} What it means
run_import (import.rs:361) spawns the file copy on a blocking thread and the JoinError from tokio::task::spawn_blocking(...).await is wrapped with this message. JoinError means the task never completed normally: it panicked, or it was cancelled at runtime shutdown. The inner copy io::Error has a separate message ('Failed to copy audio file'), so this variant is about the task itself dying, not the filesystem.
Source
Thrown at frontend/src-tauri/src/audio/import.rs:361
let meeting_folder = create_meeting_folder(&base_folder, &title, false)?;
// Copy audio file to meeting folder
emit_progress(&app, "copying", 10, "Copying audio file...");
let dest_filename = format!(
"audio.{}",
source
.extension()
.and_then(|e| e.to_str())
.unwrap_or("mp4")
);
let dest_path = meeting_folder.join(&dest_filename);
let src = source.clone();
let dst = dest_path.clone();
tokio::task::spawn_blocking(move || std::fs::copy(&src, &dst))
.await
.map_err(|e| anyhow!("Copy task join error: {}", e))?
.map_err(|e| anyhow!("Failed to copy audio file: {}", e))?;
info!("Copied audio to: {}", dest_path.display());
// Check for cancellation
if IMPORT_CANCELLED.load(Ordering::SeqCst) {
// Cleanup: remove the meeting folder
let _ = std::fs::remove_dir_all(&meeting_folder);
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);View on GitHub (pinned to 0281737d87)
Solutions
- Check the JoinError details: if it is a panic, the panic payload is in the message string - log it and fix the root cause; if cancelled, the app was shutting down, which is expected during quit.
- Harden the path: sanitize/reject incoming path strings containing NUL or control characters before spawning the copy.
- Do not quit the app mid-import - or accept that shutdown cancels the copy and clean orphan meeting folders on next launch.
- For panic resilience, replace fs::copy with an explicit read/write loop returning io::Result instead of panicking on invalid paths.
- If this recurs on shutdown, block window close while IMPORT_IN_PROGRESS is set, or move copies to a resilient queue.
Example fix
// before: fs::copy can panic on a NUL-contaminated path, killing the blocking task
tokio::task::spawn_blocking(move || std::fs::copy(&src, &dst)).await
.map_err(|e| anyhow!("Copy task join error: {}", e))?;
// after: validate the path, and copy without panic-on-invalid-input
if src.to_str().map_or(true, |s| s.contains('\0')) {
return Err(anyhow!("Invalid source path"));
}
tokio::task::spawn_blocking(move || std::fs::copy(&src, &dst)).await
.map_err(|e| anyhow!("Copy task join error: {}", e))??; Defensive patterns
Strategy: retry
Validate before calling
// Rust: reject paths that can panic fs::copy before spawning
fn copy_is_safe(p: &Path) -> bool {
p.to_str().map_or(false, |s| !s.contains('\0') && !s.contains('\u{7f}'))
}
if !copy_is_safe(&src) { return Err(anyhow!("Invalid source path")); } Try / catch
// Retry once on JoinError (transient panic/shutdown race), then surface details
let copied = match tokio::task::spawn_blocking(move || std::fs::copy(&src, &dst)).await {
Ok(r) => r,
Err(join) if join.is_panic() => {
warn!("copy panicked once: {}", join);
tokio::task::spawn_blocking(move || std::fs::copy(&src2, &dst2)).await
.map_err(|e| anyhow!("Copy task join error: {}", e))??
}
Err(join) => return Err(anyhow!("Copy task join error: {}", join)),
}?; Prevention
- Sanitize IPC-sourced path strings (no NUL/control characters) before spawn_blocking.
- Block app quit while an import is running, or accept that shutdown cancels the copy.
- Use a chunked read/write copy loop (io::Result) instead of fs::copy to eliminate panic paths.
- Clean up orphan meeting folders on next launch in case a cancelled runtime left one.
When it happens
Trigger: std::fs::copy panicking inside the blocking task (a path containing a NUL byte from a mangled IPC string is the classic panic source); the tokio runtime being shut down while the import future still awaits (app quit during import); aborting the task handle externally. Note: the cancellation flag is checked only after the copy, so a user Cancel does NOT produce this error - the copy runs to completion first.
Common situations: Closing the Tauri window / quitting mid-copy (runtime drops, spawn_blocking tasks cancelled); a path string crossing IPC with embedded control characters causing fs::copy to panic; very large files over flaky network shares where the app is closed before completion; test harnesses dropping the runtime early.
Related errors
- Decode task join error: {}
- Resample task join error: {}
- VAD task panicked: {}
- Decode task panicked: {}
- Resample task panicked: {}
AI-assisted analysis of Zackriya-Solutions/meetily@0281737d87 (2026-08-16).
Data as JSON: /api/errors/af778fa0da71503b.
Report an issue: GitHub.