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

  1. Inspect the embedded JoinError payload in logs to identify the underlying panic; report it upstream if it is a code bug
  2. Retry the download — a runtime shutdown leaves no corrupt committed state
  3. 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

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


AI-assisted analysis of cjpais/Handy@98a4d80cce (2026-08-16). Data as JSON: /api/errors/339f753fdd96cddc. Report an issue: GitHub.