cjpais/Handy · error · anyhow::Error

Failed to extract archive

Error message

Failed to extract archive: {}

What it means

Extraction of a downloaded archive-based (directory) model failed: the inner extraction block captures the error as error_msg, emits the model-extraction-failed event with the model_id and error, removes the model from the extracting set, and rethrows via `anyhow::anyhow!(error_msg)`. Extraction runs into a temp dir which is then moved to the final model dir, so failures are typically a corrupt archive or filesystem problems.

Solutions

  1. Delete the model entry and its leftover files, then re-download — a fresh archive fixes corruption
  2. Free disk space: budget for archive plus extracted content simultaneously
  3. Check temp dir and models dir permissions; on Windows, add an antivirus exclusion for the models dir
  4. Listen for the model-extraction-failed event in the UI to surface the detailed error to the user
Defensive patterns

Strategy: retry

Validate before calling

// require room for archive + extracted copy before starting
fn disk_space_sufficient(models_dir: &Path, model_bytes: u64) -> bool {
    // rough rule: 2.2x the archive size covers download + extract + move
    fs2::available_space(models_dir).map(|f| f > model_bytes * 2).unwrap_or(false)
}

Try / catch

if let Err(e) = install.extract() {
    // error_msg already emitted as model-extraction-failed; a clean re-download is the fix
    let _ = manager.delete_model(model_id);
    manager.download_model(model_id).await?; // retry once with a fresh archive
}

Prevention

When it happens

Trigger: Truncated/corrupted archive from an interrupted or bit-rotted download; unsupported compression method inside the zip; disk full during extraction (archive + extracted copy coexist); temp dir or destination permissions; antivirus/locked files on Windows mid-extract.

Common situations: Download 'completed' but the file was cut short by a stall; extracting multi-GB models on a nearly-full disk; EDR software quarantining or locking extracted binaries; extracting into a read-only or roaming-profile directory.

Related errors


AI-assisted analysis of cjpais/Handy@ba10ce1943 (2026-08-17). Data as JSON: /api/errors/7c73d6e9bffaafe1. Report an issue: GitHub.

Appendix: source

Thrown at src-tauri/src/managers/model.rs:2330

                let error_msg = format!("Failed to extract archive: {}", e);
                // Clean up failed extraction
                let _ = fs::remove_dir_all(&temp_extract_dir);
                // Delete the corrupt partial file so the next download attempt starts fresh
                // instead of resuming from a broken archive (issue #858).
                let _ = fs::remove_file(&partial_path);
                // Remove from extracting set
                {
                    let mut extracting = self.extracting_models.lock().unwrap();
                    extracting.remove(model_id);
                }
                let _ = self.app_handle.emit(
                    "model-extraction-failed",
                    &serde_json::json!({
                        "model_id": model_id,
                        "error": error_msg
                    }),
                );
                anyhow::anyhow!(error_msg)
            })?;

            // Find the actual extracted directory (archive might have a nested structure)
            let extracted_dirs: Vec<_> = fs::read_dir(&temp_extract_dir)?
                .filter_map(|entry| entry.ok())
                .filter(|entry| entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false))
                .collect();

            if extracted_dirs.len() == 1 {
                // Single directory extracted, move it to the final location
                let source_dir = extracted_dirs[0].path();
                if final_model_dir.exists() {
                    fs::remove_dir_all(&final_model_dir)?;
                }
                fs::rename(&source_dir, &final_model_dir)?;
                // Clean up temp directory
                let _ = fs::remove_dir_all(&temp_extract_dir);
            } else {

View on GitHub (pinned to ba10ce1943)