cjpais/Handy · error · anyhow::Error

Model file is incomplete

Error message

Model file is incomplete: {}

What it means

Thrown by ModelManager::get_model_path for single-file models (is_directory == false): the model file exists in the models directory, but a companion `<filename>.partial` file is also present. `.partial` is the in-progress download artifact and is only removed once the download completes and is verified, so its presence marks the model as not fully downloaded/verified and the manager will not return the path.

Solutions

  1. Delete the stale `<model_filename>.partial` file from the models directory, then retry get_model_path.
  2. If the main file itself may be truncated, delete both files and re-download the model through the app.
  3. Trigger a fresh download in the app (which will resume/redo and clean the marker) instead of hand-editing the models dir.
  4. Verify the download is not currently in progress before deleting anything.

Example fix

// before: interrupted download left the marker
ls models_dir
# ggml-base.bin  ggml-base.bin.partial
rm models_dir/ggml-base.bin.partial
// after: get_model_path returns models_dir/ggml-base.bin
Defensive patterns

Strategy: validation

Validate before calling

use std::path::Path;
fn model_file_complete(models_dir: &Path, filename: &str) -> bool {
    models_dir.join(filename).is_file()
        && !models_dir.join(format!("{}.partial", filename)).exists()
}
// if false, delete the .partial (and re-verify the file) before calling load

Type guard

fn is_complete_model_file(p: &Path) -> bool {
    p.is_file() && !p.with_file_name(format!("{}.partial", p.file_name().map(|s| s.to_string_lossy().to_string()).unwrap_or_default())).exists()
}

Try / catch

match manager.get_model_path(id) {
    Ok(p) => p,
    Err(e) if e.to_string().contains("incomplete") => {
        let partial = models_dir.join(format!("{}.partial", filename));
        let _ = std::fs::remove_file(&partial);
        manager.get_model_path(id).context("model file still incomplete")?
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling get_model_path(model_id) (directly or through load_model / load_model_with_device) for a file-based model when: (1) a prior download was interrupted (app crash, SIGKILL, power loss, disk full) leaving `<file>.partial` on disk; (2) a manual drop-in of the model file was done while an old `.partial` file remained; (3) the app's is_downloaded flag is stale relative to the actual files on disk.

Common situations: User kills the app during a large GGUF download; laptop sleeps/dies mid-download; cloud-sync (Dropbox/iCloud) restores a `.partial` file together with the model; user manually downloads a model from a mirror and drops it in the models dir while a leftover partial from a failed attempt remains.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


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

Appendix: source

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

                ));
            }
            if partial_path.exists() {
                return Err(anyhow::anyhow!(
                    "Model directory is incomplete: {}",
                    model_id
                ));
            }
            Ok(model_path)
        } else {
            if !model_path.exists() {
                self.mark_model_unavailable(model_id);
                return Err(anyhow::anyhow!(
                    "Complete model file not found: {}",
                    model_id
                ));
            }
            if partial_path.exists() {
                return Err(anyhow::anyhow!("Model file is incomplete: {}", model_id));
            }
            Ok(model_path)
        }
    }

    pub fn cancel_download(&self, model_id: &str) -> Result<()> {
        debug!("ModelManager: cancel_download called for: {}", model_id);

        // Trigger the cancellation token to stop the download. The HF path
        // aborts its in-flight chunk tasks and unwinds promptly; the URL path
        // observes it on the next chunk of its stream loop.
        {
            let flags = self.cancel_flags.lock().unwrap();
            if let Some(token) = flags.get(model_id) {
                token.cancel();
                info!("Cancellation token triggered for: {}", model_id);
            } else {
                warn!("No active download found for: {}", model_id);

View on GitHub (pinned to ba10ce1943)