{"record":{"id":"095fd41e7e3af802","repo":"cjpais/Handy","slug":"model-failed-to-load-after-auto-load-attempt-plea","errorCode":null,"errorMessage":"Model failed to load after auto-load attempt. Please check your model settings.","messagePattern":"Model failed to load after auto-load attempt\\. Please check your model settings\\.","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/src/managers/transcription.rs","lineNumber":1262,"sourceCode":"        // Non-whisper archs (e.g. Voxtral Small) can advertise\n        // Feature::InitialPrompt yet reject the whisper-kind run extension\n        // with INVALID_ARG, so the whisper extension must be gated on the\n        // arch, not on the feature (see #1601).\n        let mut model_is_whisper = false;\n\n        // Perform transcription with the appropriate engine.\n        // We use catch_unwind to prevent engine panics from poisoning the mutex,\n        // which would make the app hang indefinitely on subsequent operations.\n        let (result, output_language, model_languages) = {\n            let mut engine_guard = self.lock_engine();\n\n            // Take the engine out so we own it during transcription.\n            // If the engine panics, we simply don't put it back (effectively unloading it)\n            // instead of poisoning the mutex.\n            let mut engine = match engine_guard.take() {\n                Some(e) => e,\n                None => {\n                    return Err(anyhow::anyhow!(\n                        \"Model failed to load after auto-load attempt. Please check your model settings.\"\n                    ));\n                }\n            };\n\n            // Release the lock before transcribing — no mutex held during the engine call\n            drop(engine_guard);\n\n            // Probe live transcribe-cpp capabilities once (cheap GGUF-metadata\n            // reads); the loaded session is the source of truth, not the\n            // ModelManager copy. The whisper run extension is kind-tagged, so\n            // non-whisper archs (parakeet, voxtral, …) reject it with\n            // INVALID_ARG; attach it — and translate — only where supported.\n            let mut model_supports_translate = false;\n            let mut model_languages = self\n                .model_manager\n                .get_model_info(&active_model)\n                .map(|info| info.supported_languages)","sourceCodeStart":1244,"sourceCodeEnd":1280,"githubUrl":"https://github.com/cjpais/Handy/blob/ba10ce1943ef34e93c09494027fc0b9ced2e8a44/src-tauri/src/managers/transcription.rs#L1244-L1280","documentation":"Inside the transcription block, engine_guard.take() moves the engine out of the mutex before the lock is dropped for the inference call. If take() returns None — engine present at the earlier check (line 1181) but gone by now — this error fires. It is a race guard, not a literal auto-load failure: between the two lock acquisitions another thread switched or unloaded the model, or a prior panicking transcription left the slot empty.","triggerScenarios":"Model switched or unloaded concurrently with transcribe() (settings change, --transcribe-file with a different --model, idle-unload timer firing in the window); a previous engine panic removed the engine and the state checks raced.","commonSituations":"Recording while changing the selected model in the UI; CLI file-transcription with an explicit --model racing a live recording; test harnesses driving transcribe() and load/unload from parallel threads.","solutions":["Retry the transcription after the model switch/unload completes — the new model loads and takes the slot","Avoid issuing transcribe calls while changing models (serialize load/unload and transcribe in calling code)","If it recurs without switching, look for a preceding 'Transcription engine panicked' error (the panic path leaves the slot empty)","Check that only one component (overlay vs CLI vs tests) drives the manager at a time"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// serialize model lifecycle changes against transcribe()\nlet _guard = manager.lifecycle_lock(); // mutual-excludes load/unload/switch\nlet text = manager.transcribe(audio)?;","typeGuard":"fn engine_resident(m: &TranscriptionManager) -> bool {\n    m.lock_engine().is_some() // non-generic check right before take()\n}","tryCatchPattern":"match manager.transcribe(audio) {\n    Err(e) if e.to_string().starts_with(\"Model failed to load after auto-load attempt\") => {\n        // transient race: wait for the switch to settle, then retry once\n        manager.wait_until_model_settled();\n        manager.transcribe(audio)\n    }\n    other => other,\n}","preventionTips":["Do not change the selected model while a recording is being processed","Serialize CLI --transcribe-file runs against live recordings (single-instance flags help)","Treat this error as retryable — it only fires in the switch/unload race window"],"tags":["rust","race-condition","model-state","lifecycle"],"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"}