cjpais/Handy · error · anyhow::Error
Model failed to load after auto-load attempt. Please check…
Error message
Model failed to load after auto-load attempt. Please check your model settings.
What it means
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.
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
Defensive patterns
Strategy: validation
Validate before calling
// serialize model lifecycle changes against transcribe() let _guard = manager.lifecycle_lock(); // mutual-excludes load/unload/switch let text = manager.transcribe(audio)?;
Type guard
fn engine_resident(m: &TranscriptionManager) -> bool {
m.lock_engine().is_some() // non-generic check right before take()
} Try / catch
match manager.transcribe(audio) {
Err(e) if e.to_string().starts_with("Model failed to load after auto-load attempt") => {
// transient race: wait for the switch to settle, then retry once
manager.wait_until_model_settled();
manager.transcribe(audio)
}
other => other,
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Model is not loaded for transcription.
- Model is currently downloading
- Canary transcription failed
- Cohere transcription failed
- Complete model directory not found
AI-assisted analysis of cjpais/Handy@ba10ce1943 (2026-08-17).
Data as JSON: /api/errors/095fd41e7e3af802.
Report an issue: GitHub.
Appendix: source
Thrown at src-tauri/src/managers/transcription.rs:1262
// 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 ba10ce1943)