{"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":1208,"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":1190,"sourceCodeEnd":1226,"githubUrl":"https://github.com/cjpais/Handy/blob/ba10ce1943ef34e93c09494027fc0b9ced2e8a44/src-tauri/src/managers/transcription.rs#L1190-L1226","documentation":"transcribe() first waits on a condvar until any in-progress model load finishes, then requires the engine slot to be occupied. This error means the engine is None at that point: no model was ever loaded, the last load failed, the model was unloaded (idle timeout / manual unload / model switch), or the engine was dropped after a prior panic. Despite the comment, the current code performs no auto-load — it errors immediately.","triggerScenarios":"Global shortcut triggers transcription before the first model finishes downloading/loading; the selected model's load failed (a loading_failed event preceded this); ModelUnloadTimeout elapsed since last use and the engine was unloaded; a previous transcription panicked (error 57) and the engine was not returned.","commonSituations":"Fresh install with no model downloaded yet; user switched models in settings and recorded during the swap; aggressive unload-timeout setting on a slow machine; earlier load failure user dismissed.","solutions":["Open settings and confirm a model is downloaded and selected; wait for the model-state-changed \"loaded\" event before recording","Check logs/app notifications for a preceding loading_failed error and fix that first (usually re-download)","Increase or disable the model unload timeout if the engine keeps getting evicted between recordings","If it follows a panic error, simply retry — the next attempt reloads the model"],"exampleFix":"// before: record blind, fail when idle-unload evicted the engine\nlet text = manager.transcribe(audio)?;\n\n// after: ensure a model is active first (loads synchronously if needed)\nif manager.get_current_model().is_none() {\n    manager.load_model_blocking(&settings.selected_model, None)?;\n}\nlet text = manager.transcribe(audio)?;","handlingStrategy":"validation","validationCode":"// before triggering a recording: ensure a model is actually resident\nif manager.get_current_model().is_none() {\n    manager.load_model_blocking(&settings.selected_model, None)?;\n}","typeGuard":"// Rust analog of a type guard: narrow on manager state before transcribe()\nfn model_ready(m: &TranscriptionManager) -> bool {\n    m.get_current_model().is_some() && !m.is_loading()\n}","tryCatchPattern":"match manager.transcribe(audio) {\n    Err(e) if e.to_string() == \"Model is not loaded for transcription.\" => {\n        manager.load_model_blocking(&settings.selected_model, None)?;\n        manager.transcribe(audio) // one retry after load\n    }\n    other => other,\n}","preventionTips":["Gate the record shortcut on the frontend's model-ready state (model-state-changed \"loaded\")","Raise or disable the model unload timeout if evictions happen between recordings","Handle download completion before allowing first recording on a fresh install","After any load failure, re-trigger a load before the next record attempt"],"tags":["rust","model-state","lifecycle","transcription"],"backgroundTag":"model-not-loaded","analyzedSha":"ba10ce1943ef34e93c09494027fc0b9ced2e8a44","analyzedAt":"2026-08-17T10:29:55.597Z","contentChangedAt":"2026-08-17T10:29:55.597Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}