cjpais/Handy · error · anyhow::Error
Model failed to load after auto-load attempt. Please check y
Error message
Model failed to load after auto-load attempt. Please check your model settings.
What it means
A check-then-use race inside transcribe(): the engine existed at the first check (engine_guard.is_none() was false), but by the time the code re-locked and called engine_guard.take() the slot was empty. Something unloaded or switched the model between the two locks — load_model drops the old engine before building the new one, the idle watcher can unload, and the panic-recovery path deliberately leaves the slot empty. The (slightly stale) message tells the user to check model settings; the next transcription attempt will trigger a fresh load.
Source
Thrown at src-tauri/src/managers/transcription.rs:1236
// Non-whisper archs (e.g. Voxtral Small) can advertise
// Feature::InitialPrompt yet reject the whisper-kind run extension
// with INVALID_ARG, so the whisper extension must be gated on the
// arch, not on the feature (see #1601).
let mut model_is_whisper = false;
// Perform transcription with the appropriate engine.
// We use catch_unwind to prevent engine panics from poisoning the mutex,
// which would make the app hang indefinitely on subsequent operations.
let (result, output_language, model_languages) = {
let mut engine_guard = self.lock_engine();
// Take the engine out so we own it during transcription.
// If the engine panics, we simply don't put it back (effectively unloading it)
// instead of poisoning the mutex.
let mut engine = match engine_guard.take() {
Some(e) => e,
None => {
return Err(anyhow::anyhow!(
"Model failed to load after auto-load attempt. Please check your model settings."
));
}
};
// Release the lock before transcribing — no mutex held during the engine call
drop(engine_guard);
// Probe live transcribe-cpp capabilities once (cheap GGUF-metadata
// reads); the loaded session is the source of truth, not the
// ModelManager copy. The whisper run extension is kind-tagged, so
// non-whisper archs (parakeet, voxtral, …) reject it with
// INVALID_ARG; attach it — and translate — only where supported.
let mut model_supports_translate = false;
let mut model_languages = self
.model_manager
.get_model_info(&active_model)
.map(|info| info.supported_languages)View on GitHub (pinned to 98a4d80cce)
Solutions
- Simply retry the transcription — the next attempt re-checks and auto-loads the model
- Avoid switching models while a recording is in progress; queue the switch until after output
- Lengthen or disable idle auto-unload if the race recurs
- If it happens without any switching, inspect logs for a preceding 'engine panicked' unload
Example fix
// before
let text = tm.transcribe(audio)?; // can hit the take() race mid-call
// after — retry once; the retry path waits for/starts a fresh load
let text = match tm.transcribe(audio.clone()) {
Ok(t) => t,
Err(e) if e.to_string().contains("auto-load attempt") => {
tm.initiate_model_load();
wait_for_loading_completion(&app_handle);
tm.transcribe(audio)?
}
Err(e) => return Err(e),
}; Defensive patterns
Strategy: retry
Validate before calling
// Reduce the race window: re-check right before the call and avoid model switches while recording
if recording_in_progress() { defer_model_switch_until_idle(); }
if !tm.is_model_loaded() { tm.initiate_model_load(); wait_for_loading_completion(&app_handle); } Try / catch
match tm.transcribe(audio.clone()) {
Err(e) if e.to_string().contains("auto-load attempt") => {
// Engine was swapped/unloaded mid-call; next attempt reloads — retry once
thread::sleep(Duration::from_millis(250));
tm.initiate_model_load();
wait_for_loading_completion(&app_handle);
tm.transcribe(audio)
}
other => other,
} Prevention
- Never switch models while a recording is being finalized; queue the switch
- Lengthen the idle-unload timeout in settings for long pause/resume workflows
- In automation scripts, serialize load and transcribe steps rather than running them concurrently
- Treat repeat occurrences as a symptom of an underlying load failure and inspect the loading_failed event
When it happens
Trigger: Another thread calls load_model() (model switch) between the loaded-check at line 1181 and the take() at line 1233; idle watcher unloads the model mid-transcribe setup; a prior panicking call cleared current_model_id and the engine; CLI --transcribe-file races a settings-triggered reload.
Common situations: User changes the model in the UI right as a recording ends; push-to-talk released at the same moment the idle timeout fires; automated scripts toggling models while transcribing; flaky retest right after a panic unload.
Related errors
- Model is not loaded for transcription.
- Model not downloaded
- Transcription engine panicked: {}. The model has been unload
- threshold must be between 0.0 and 1.0
- Failed to create VAD: {e}
AI-assisted analysis of cjpais/Handy@98a4d80cce (2026-08-16).
Data as JSON: /api/errors/095fd41e7e3af802.
Report an issue: GitHub.