cjpais/Handy · error · anyhow::Error
SHA256 task panicked: {}
Error message
SHA256 task panicked: {} What it means
verify_file_with_events runs the SHA256 check inside tokio::task::spawn_blocking; this error wraps the JoinError returned when that blocking task panicked or was cancelled instead of yielding a Result. The JoinError details are embedded, so the underlying panic can be identified from logs.
Source
Thrown at src-tauri/src/managers/model/download.rs:117
Ok(format!("{:x}", hasher.finalize()))
}
/// Emit verification events around a blocking sha256 check of `path`.
/// On mismatch `verify_sha256` deletes the file, so the next attempt (or
/// next source) starts clean. A `None` hash skips checking (custom models).
async fn verify_file_with_events(
model_id: &str,
path: &Path,
expected_sha256: Option<&str>,
emit: &(dyn Fn(HttpDownloadEvent<'_>) + Send + Sync),
) -> Result<()> {
emit(HttpDownloadEvent::VerificationStarted);
let path = path.to_path_buf();
let expected = expected_sha256.map(str::to_string);
let id = model_id.to_string();
tokio::task::spawn_blocking(move || Self::verify_sha256(&path, expected.as_deref(), &id))
.await
.map_err(|e| anyhow::anyhow!("SHA256 task panicked: {}", e))??;
emit(HttpDownloadEvent::VerificationCompleted);
Ok(())
}
/// [`Self::download_http_resumable_with_events`] wired to the Tauri event
/// bus — the production entry point.
pub(super) async fn download_http_resumable(
&self,
model_id: &str,
url: &str,
partial_path: &Path,
expected_size: Option<u64>,
expected_sha256: Option<&str>,
cancel_token: &CancellationToken,
) -> Result<HttpDownloadOutcome> {
let app_handle = self.app_handle.clone();
let id = model_id.to_string();
Self::download_http_resumable_with_events(View on GitHub (pinned to 98a4d80cce)
Solutions
- Inspect the embedded JoinError payload in logs to identify the underlying panic; report it upstream if it is a code bug
- Retry the download — a runtime shutdown leaves no corrupt committed state
- Avoid killing the app during verification; use cancel_download for a clean stop
Defensive patterns
Strategy: try-catch
Try / catch
let result = tokio::task::spawn_blocking(move || verify_sha256(&path, expected, &id)).await;
match result {
Ok(inner) => inner?, // real verification result
Err(join_err) => {
// task panicked or was cancelled at shutdown — safe to retry once
warn!("SHA256 task failed: {}", join_err);
if !app_shutting_down() { retry_verification().await?; }
anyhow::bail!("SHA256 task panicked: {}", join_err);
}
} Prevention
- Avoid killing the app during the verification phase — use cancel_download instead
- Log the JoinError payload; a recurring panic is a code bug worth reporting
- Keep the tokio runtime alive until in-flight spawn_blocking downloads finish
When it happens
Trigger: A panic inside verify_sha256 on unexpected file state; the tokio runtime shutting down (app quit) while verification of a multi-GB file is in flight; blocking-pool task aborted at runtime teardown.
Common situations: Quitting the app during the final verification phase of a large model download; edge-case bugs in the hashing path; forced process termination during spawn_blocking work.
Related errors
- Download verification failed for model {}: file is corrupt.
- Failed to verify download for model {}: {}. Please retry.
- Transcription engine panicked: {}. The model has been unload
AI-assisted analysis of cjpais/Handy@98a4d80cce (2026-08-16).
Data as JSON: /api/errors/339f753fdd96cddc.
Report an issue: GitHub.