Build: 1.7.0 (local build from main + #1221/#1230/#1232/#1234), macOS 26.4.1, Groq whisper-large-v3 primary with ElevenLabs scribe_v2 as the automatic recovery fallback (hedge enabled at 3 s).
What happened: after stopping a ~20 s dictation the notch indicator showed "Transcribing…" and never left that state. No error was logged (error-log.json untouched), no history record was written, and no toast appeared — the only way out was pressing Escape to cancel. (On this machine the Escape then hit the display-cycle crash tracked in #1229, but that is a separate defect.)
Why the existing safeguards don't cover it: HostServices gives cloud requests a 30 s timeoutIntervalForRequest, and the hedge race (#1230) covers a primary that is slow but answering. A request that stalls after the server has started responding, or two engines that both hang the same way, is not bounded by anything: transcribeFinalAudio awaits the primary/hedge/fallback chain with no overall deadline, so DictationViewModel stays in .processing forever.
Expected: the final-transcription phase should have an upper bound. When it is exceeded the in-flight requests should be cancelled, the user should see an error, and the recording should be kept in Dictation Recovery so nothing is lost.
A PR follows.
Build: 1.7.0 (local build from main + #1221/#1230/#1232/#1234), macOS 26.4.1, Groq whisper-large-v3 primary with ElevenLabs scribe_v2 as the automatic recovery fallback (hedge enabled at 3 s).
What happened: after stopping a ~20 s dictation the notch indicator showed "Transcribing…" and never left that state. No error was logged (error-log.json untouched), no history record was written, and no toast appeared — the only way out was pressing Escape to cancel. (On this machine the Escape then hit the display-cycle crash tracked in #1229, but that is a separate defect.)
Why the existing safeguards don't cover it:
HostServicesgives cloud requests a 30 stimeoutIntervalForRequest, and the hedge race (#1230) covers a primary that is slow but answering. A request that stalls after the server has started responding, or two engines that both hang the same way, is not bounded by anything:transcribeFinalAudioawaits the primary/hedge/fallback chain with no overall deadline, soDictationViewModelstays in.processingforever.Expected: the final-transcription phase should have an upper bound. When it is exceeded the in-flight requests should be cancelled, the user should see an error, and the recording should be kept in Dictation Recovery so nothing is lost.
A PR follows.