cjpais/Handy · error · anyhow::Error

Model not downloaded

Error message

Model not downloaded

What it means

The model exists in the catalog but its is_downloaded flag is false: no complete copy is registered on this machine. load_model emits a loading_failed 'model-state-changed' event carrying this message and aborts before ever calling get_model_path or touching the engine.

Source

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

        );

        let model_info = self
            .model_manager
            .get_model_info(model_id)
            .ok_or_else(|| anyhow::anyhow!("Model not found: {}", model_id))?;

        if !model_info.is_downloaded {
            let error_msg = "Model not downloaded";
            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()),
                },
            );
            return Err(anyhow::anyhow!(error_msg));
        }

        let model_path = self.model_manager.get_model_path(model_id)?;

        // 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;
        }

        // Create appropriate engine based on model type

View on GitHub (pinned to 98a4d80cce)

Solutions

  1. Call download_model(model_id) and wait for the completion event before loading
  2. Pre-check get_model_info(model_id).is_downloaded and gate the load on it
  3. If a previously downloaded model shows as not downloaded, run rescan_local_models to rebuild state from disk

Example fix

// before
self.transcription_manager.load_model(model_id.clone()).await?;

// after
let info = self.model_manager.get_model_info(&model_id)
    .ok_or_else(|| anyhow::anyhow!("Model not found: {}", model_id))?;
if !info.is_downloaded {
    self.model_manager.download_model(&model_id).await?; // wait for completion event
}
self.transcription_manager.load_model(model_id.clone()).await?;
Defensive patterns

Strategy: validation

Validate before calling

let Some(info) = model_manager.get_model_info(model_id) else {
    anyhow::bail!("Model not found: {}", model_id);
};
if !info.is_downloaded {
    model_manager.download_model(model_id).await?; // wait for completion event
}

Type guard

fn is_model_downloadable(mm: &ModelManager, id: &str) -> bool {
    mm.get_model_info(id).map(|i| i.is_downloaded).unwrap_or(false)
}

Try / catch

match transcription_manager.load_model(model_id.clone()).await {
    Ok(()) => Ok(()),
    Err(e) if e.to_string() == "Model not downloaded" => {
        // download then retry the load once
        model_manager.download_model(&model_id).await?;
        transcription_manager.load_model(model_id.clone()).await
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: The user or startup auto-load selects a model whose download never ran, failed, or was cancelled; the is_downloaded flag was reset after cache eviction; a load request raced the download start.

Common situations: Fresh installs where the preferred model download was skipped or interrupted; selecting an alternative model before it finishes downloading; state desync after files were removed externally.

Related errors


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