cjpais/Handy · error · anyhow::Error
Timed out waiting for live transcription to finalize
Error message
Timed out waiting {:?} for live transcription to finalize What it means
finalize_stream sends StreamCmd::Finalize to the streaming worker and blocks on reply_rx.recv_timeout for STREAM_FINALIZE_REPLY_TIMEOUT (30s). A Timeout means the worker did not reply in time — typically because flushing the accumulated stream and running final inference took longer than 30s. On timeout the code clears stream_active and returns this error; the worker may still hold the engine, so callers must surface the error rather than immediately starting a batch fallback.
Solutions
- Retry the recording — the timed-out worker eventually finishes or exits and the engine state recovers on subsequent loads
- Shorten live sessions or check the stream perf logs (logged every STREAM_PERF_LOG_INTERVAL = 5s) to see how far behind the worker is
- Switch the accelerator setting (GPU to CPU or back) or use a lighter model if finalize is consistently slow
- Rebuild with a larger STREAM_FINALIZE_REPLY_TIMEOUT if long sessions are the norm
- Check system logs for GPU driver resets if the timeout correlates with display issues
Example fix
// before: 30s hard limit const STREAM_FINALIZE_REPLY_TIMEOUT: Duration = Duration::from_secs(30); // after: allow long sessions to finalize const STREAM_FINALIZE_REPLY_TIMEOUT: Duration = Duration::from_secs(120);
Defensive patterns
Strategy: fallback
Try / catch
match manager.finalize_stream() {
Ok(Some(text)) => insert_text(text),
Ok(None) => { /* no stream was active: batch-transcribe the buffer */ }
Err(e) if e.to_string().contains("Timed out waiting") => {
// worker may still hold the engine — surface to the user, do NOT
// immediately batch-fallback (per finalize_stream's contract)
notify_user_timeout();
}
Err(e) => notify_user(e),
} Prevention
- End live sessions periodically on slow hardware instead of one very long take
- Watch the 5s stream perf logs to detect the worker falling behind before it times out
- Pick a quantization/model weight your machine can finalize within 30s
- If timeouts correlate with GPU driver resets, run with CPU accelerator
When it happens
Trigger: User stops a long live-transcription session and the streaming model's finalize inference exceeds 30s: very long buffered audio, CPU starvation (heavy foreground app), GPU hang/stall in the ONNX provider, or the worker thread busy draining a large Feed backlog before the FIFO-ordered Finalize command is even reached.
Common situations: Long dictation sessions on slow hardware; system under load (builds, games); GPU driver reset mid-inference; default quantization of the streaming model being too heavy for the machine.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Failed to load moonshine streaming model
- Moonshine streaming transcription failed
- Failed to load moonshine model
- Moonshine transcription failed
- transfer stalled: no progress for
AI-assisted analysis of cjpais/Handy@ba10ce1943 (2026-08-17).
Data as JSON: /api/errors/ec8bc7d2a0202039.
Report an issue: GitHub.
Appendix: source
Thrown at src-tauri/src/managers/transcription.rs:1129
/// `Ok(None)` means no usable stream was active and the caller may fall back
/// to batch transcription. `Err` means finalize itself failed or timed out.
/// A timeout may still leave the worker holding the engine, so callers
/// should surface it instead of immediately starting a batch fallback.
pub fn finalize_stream(&self) -> Result<Option<String>> {
let Some(tx) = self.router.take() else {
return Ok(None);
};
let (reply_tx, reply_rx) = mpsc::channel();
if tx.send(StreamCmd::Finalize(reply_tx)).is_err() {
return Ok(None);
}
let finalized = match reply_rx.recv_timeout(STREAM_FINALIZE_REPLY_TIMEOUT) {
Ok(Some(finalized)) => finalized,
Ok(None) => return Ok(None),
Err(mpsc::RecvTimeoutError::Disconnected) => return Ok(None),
Err(mpsc::RecvTimeoutError::Timeout) => {
self.stream_active.store(false, Ordering::Release);
return Err(anyhow::anyhow!(
"Timed out waiting {:?} for live transcription to finalize",
STREAM_FINALIZE_REPLY_TIMEOUT
));
}
};
let settings = get_settings(&self.app_handle);
// Streaming models do not receive a decode prompt, so custom words
// always go through the shared fuzzy post-correction path.
let filtered = post_process_transcription_text(
finalized.text,
&settings,
false,
&finalized.output_language,
&finalized.supported_languages,
);
self.maybe_unload_immediately("streaming transcription");View on GitHub (pinned to ba10ce1943)