{"record":{"id":"804d59fc741194d4","repo":"cjpais/Handy","slug":"model-is-not-loaded-for-transcription","errorCode":null,"errorMessage":"Model is not loaded for transcription.","messagePattern":"Model is not loaded for transcription\\.","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/src/managers/transcription.rs","lineNumber":1182,"sourceCode":"        debug!(\"Audio vector length: {}\", audio_len);\n\n        if audio.is_empty() {\n            debug!(\"Empty audio vector\");\n            self.maybe_unload_immediately(\"empty audio\");\n            return Ok(String::new());\n        }\n\n        // Check if model is loaded, if not try to load it\n        {\n            // If the model is loading, wait for it to complete.\n            let mut is_loading = self.is_loading.lock().unwrap();\n            while *is_loading {\n                is_loading = self.loading_condvar.wait(is_loading).unwrap();\n            }\n\n            let engine_guard = self.lock_engine();\n            if engine_guard.is_none() {\n                return Err(anyhow::anyhow!(\"Model is not loaded for transcription.\"));\n            }\n        }\n\n        // Get current settings for configuration\n        let settings = get_settings(&self.app_handle);\n\n        // Validate selected language against the model's supported languages.\n        // If the language isn't supported, fall back to \"auto\" to prevent errors.\n        // Validate against the model that's actually loaded (which can differ\n        // from settings.selected_model when a caller loaded a specific model —\n        // e.g. the --transcribe-file path's --model), not the persisted\n        // selection.\n        let active_model = self\n            .get_current_model()\n            .unwrap_or_else(|| settings.selected_model.clone());\n        // Resolve the persisted language *intent* into the language this model\n        // will actually use. The coercion is capability-aware (a must-pick model\n        // never receives \"auto\") and computed fresh here — it is never written","sourceCodeStart":1164,"sourceCodeEnd":1200,"githubUrl":"https://github.com/cjpais/Handy/blob/98a4d80cce8ad41efec2a419b59d9e81229a35d7/src-tauri/src/managers/transcription.rs#L1164-L1200","documentation":"transcribe() requires a loaded engine: after waiting on the loading condvar (so any in-flight load finishes), the engine slot is still None, so the call fails immediately. This happens when the initial load failed (you also saw a 'Failed to load ... model' error and a loading_failed event), when the idle watcher unloaded the model, or after a panic unload — and nothing re-initiated a load before transcribe() was called. Note load_model drops the old engine before building the new one, so a failed model switch also leaves the slot empty.","triggerScenarios":"transcribe() invoked while engine_guard.is_none(): recording started before the first model download finished (or after it failed); model auto-unloaded by the idle watcher and the shortcut path called transcribe() without initiate_model_load(); previous load_model failed mid-switch after dropping the old engine (lines 526-533); engine was dropped by the panic-recovery path in error 57.","commonSituations":"First run with no model downloaded yet; persisted selected_model points at a model whose files were deleted; idle-unload enabled with aggressive timeout; user switched models right as a recording ended; earlier load failed due to missing/corrupt files.","solutions":["Trigger a load before transcribing: call initiate_model_load() (or simply start a new recording, which kicks the load) and wait for the loading_completed event","Check the last loading_failed model-state-changed event — the underlying load error (missing files, OOM) must be fixed first","Verify settings.selected_model still references an installed model; re-select or re-download it","Disable or lengthen idle auto-unload if recordings frequently race the unload watcher","If it recurs after a panic, see the 'Transcription engine panicked' error — the unload there is intentional"],"exampleFix":"// before\nlet text = tm.transcribe(audio)?; // fails with 'Model is not loaded for transcription.'\n\n// after — ensure a load is in flight and wait for it\nif !tm.is_model_loaded() {\n    tm.initiate_model_load(); // background load; UI shows model-state-changed events\n    wait_for_loading_completion(&app_handle); // block/await until loading_completed or loading_failed\n}\nanyhow::ensure!(tm.is_model_loaded(), \"model failed to load — check model settings\");\nlet text = tm.transcribe(audio)?;","handlingStrategy":"validation","validationCode":"// Ensure a model is loaded (or a load is in flight) before transcribing\nif !tm.is_model_loaded() {\n    tm.initiate_model_load();\n    wait_for_model_state(&app_handle, |ev| ev.event_type == \"loading_completed\" || ev.event_type == \"loading_failed\")?;\n}\nanyhow::ensure!(tm.is_model_loaded(), \"model unavailable — fix the load error first\");","typeGuard":"// Narrow on the runtime model state before calling transcribe\nfn engine_ready(tm: &TranscriptionManager) -> bool {\n    tm.is_model_loaded() // true only when the engine slot is occupied\n}","tryCatchPattern":"match tm.transcribe(audio) {\n    Err(e) if e.to_string().contains(\"Model is not loaded\") => {\n        tm.initiate_model_load();\n        wait_for_loading_completion(&app_handle);\n        tm.transcribe(audio) // retry after load\n    }\n    other => other,\n}","preventionTips":["Always call initiate_model_load() at recording start and await the loading_completed event before transcribing","React to model-state-changed/loading_failed events by surfacing the cause instead of letting transcribe fail later","Disable or lengthen idle auto-unload if recordings race the unload watcher","Keep settings.selected_model pointing at a model that is actually installed and loads cleanly"],"tags":["rust","tauri","state","model-lifecycle","transcription"],"backgroundTag":"model-not-loaded","analyzedSha":"98a4d80cce8ad41efec2a419b59d9e81229a35d7","analyzedAt":"2026-08-16T20:58:09.966Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}