cjpais/Handy · error · anyhow::Error

error_msg (dynamic: model loading failure message)

Error message

error_msg (dynamic: model loading failure message)

What it means

In TranscriptionManager::load_model_with_device, when get_model_info(model_id) returns None the manager formats a dynamic "Model not found: <id>" message, emits a model-state-changed 'loading_failed' event so the frontend leaves its loading state, and returns this error via anyhow. It means the requested model_id does not correspond to any model known to the ModelManager.

Solutions

  1. Fix the model_id string: enumerate valid ids via the model manager's list/info API (or the model-selector UI) and use one of those exact ids.
  2. Reset or edit the persisted settings so they reference an existing model id, then reload.
  3. If the id comes from stored state after an app update, re-select the model in the UI to refresh the stored id.
  4. Wrap the load call and listen for the 'model-state-changed' loading_failed event in the frontend to recover UI state (the event is always emitted before this error propagates).

Example fix

// before
transcription.load_model("whisper-base-q5")?; // typo, no such id
// after
let id = model_manager.list_models()
    .into_iter()
    .find(|m| m.name.contains("Base"))
    .map(|m| m.id)
    .ok_or_else(|| anyhow::anyhow!("no suitable model installed"))?;
transcription.load_model(&id)?;
Defensive patterns

Strategy: validation

Validate before calling

// Rust: verify the id exists before loading
if model_manager.get_model_info(model_id).is_none() {
    return Err(anyhow::anyhow!("unknown model id: {}", model_id));
}
transcription.load_model(model_id)?;

// Frontend: check against the catalog first
const known = models.some(m => m.id === modelId);
if (!known) throw new Error(`unknown model id: ${modelId}`);

Type guard

// Frontend narrowing: only pass ids from the typed catalog
function isKnownModelId(
  id: string,
  models: { id: string }[],
): id is string {
  return models.some((m) => m.id === id);
}

Try / catch

// Rust
if let Err(e) = transcription.load_model(model_id) {
    log::error!("load failed: {e}");
    // frontend also receives model-state-changed 'loading_failed' here
    fallback_to_default_model()?;
}

// Frontend
listen('model-state-changed', (ev) => {
  if (ev.payload.event_type === 'loading_failed') resetLoadingUi(ev.payload.error);
});

Prevention

When it happens

Trigger: Calling load_model(model_id) or load_model_with_device(model_id, device) with an id that has no entry in the model registry: a typo'd/hardcoded id, an id from an older app version whose model list changed, a stale id persisted in settings or passed from the frontend before the model catalog initialized.

Common situations: Settings file or persisted store still references a model id removed/renamed after an app update; frontend sends an id from an outdated bindings/types list; third-party scripts invoking remote-control flags with an invalid model id; plugin/custom code constructing ids by hand instead of enumerating available models.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


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

Appendix: source

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

                model_name: None,
                error: None,
            },
        );

        let model_info = match self.model_manager.get_model_info(model_id) {
            Some(model_info) => model_info,
            None => {
                let error_msg = format!("Model not found: {}", model_id);
                let _ = self.app_handle.emit(
                    "model-state-changed",
                    ModelStateEvent {
                        event_type: "loading_failed".to_string(),
                        model_id: Some(model_id.to_string()),
                        model_name: None,
                        error: Some(error_msg.clone()),
                    },
                );
                return Err(anyhow::anyhow!(error_msg));
            }
        };

        // 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 {

View on GitHub (pinned to ba10ce1943)