cjpais/Handy · error · anyhow::Error

Model not downloaded

Error message

Model not downloaded

What it means

Returned when the requested model exists in the catalog but is_downloaded is false — there is nothing on disk to load. Unlike the not-found case, this branch emits a 'loading_failed' model-state-changed event before returning, so the UI is informed. It is a sequencing error: load was requested before download completed (or after the files were removed).

Solutions

  1. Download the model first and wait for the download-completed event before calling load.
  2. Re-download via the model-selector if files were deleted externally.
  3. In UI flows, gate the 'use this model' action on the downloaded state rather than optimistic loading.
  4. If the flag disagrees with disk (shows downloaded but files gone), trigger a re-download or state refresh.

Example fix

// before
manager.load_model(model_id).await?; // errors if not downloaded

// after — ensure download state before loading
if !manager.get_model_info(model_id).unwrap().is_downloaded {
    manager.download_model(model_id, None).await?;
}
manager.load_model(model_id).await?;
Defensive patterns

Strategy: validation

Validate before calling

// Ensure download state before loading
let info = model_manager.get_model_info(model_id)
    .ok_or_else(|| anyhow::anyhow!("unknown model id {model_id}"))?;
if !info.is_downloaded {
    manager.download_model(model_id, None).await?; // wait for completion
}
manager.load_model(model_id).await?;

Type guard

fn is_ready_to_load(info: &ModelInfo) -> bool {
    info.is_downloaded
}

Try / catch

match manager.load_model(model_id).await {
    Ok(_) => Ok(()),
    Err(e) if e.to_string() == "Model not downloaded" => {
        manager.download_model(model_id, None).await?;
        manager.load_model(model_id).await
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: Calling load_model immediately after triggering a download without awaiting completion; deleting model files externally while the catalog flag stays true, then a state refresh flips it; new install where a default model was configured but never fetched.

Common situations: First-run flows racing onboarding; users clearing the models cache folder; app data migration losing model files; settings restored from another machine.

Related errors


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

Appendix: source

Thrown at src-tauri/src/managers/transcription.rs:535

        // Every failure after loading starts must emit a terminal event so the
        // frontend can never remain in its loading state.
        let emit_loading_failed = |error_msg: &str| {
            let _ = self.app_handle.emit(
                "model-state-changed",
                ModelStateEvent {
                    event_type: "loading_failed".to_string(),
                    model_id: Some(model_id.to_string()),
                    model_name: Some(model_info.name.clone()),
                    error: Some(error_msg.to_string()),
                },
            );
        };

        if !model_info.is_downloaded {
            let error_msg = "Model not downloaded";
            emit_loading_failed(error_msg);
            return Err(anyhow::anyhow!(error_msg));
        }

        let model_path = self
            .model_manager
            .get_model_path(model_id)
            .inspect_err(|error| emit_loading_failed(&error.to_string()))?;

        // Drop the current engine BEFORE building the new one so transcribe-cpp
        // frees the previous native context first — avoids holding two models at
        // once (peak memory on large GGUFs). Clear the id too: if the new load
        // fails, status should read "no loaded model", not the dropped engine.
        {
            let mut engine = self.lock_engine();
            *engine = None;
        }
        {
            let mut current_model = self.current_model_id.lock().unwrap();
            *current_model = None;

View on GitHub (pinned to ba10ce1943)