Skip to content

fix: transcription pipeline hangs forever with audio_feedback enabled (WASAPI stall + unbounded recorder waits)#1713

Open
kydechuk01 wants to merge 5 commits into
cjpais:mainfrom
kydechuk01:fix/audio-pipeline-hang
Open

fix: transcription pipeline hangs forever with audio_feedback enabled (WASAPI stall + unbounded recorder waits)#1713
kydechuk01 wants to merge 5 commits into
cjpais:mainfrom
kydechuk01:fix/audio-pipeline-hang

Conversation

@kydechuk01

Copy link
Copy Markdown

Before Submitting This PR

Please confirm you have done the following:

If this is a feature or change that was previously closed/rejected: N/A — bug fix requested by the maintainer in #1712.

Human Written Description

Thanks for a great product! But on my setup transcription kept hanging all the time, so I ended up making a few patches with Claude — it really helped, and I want to share.

(author's own words, translated from Russian)

Related Issues/Discussions

Fixes #1712
Likely the same underlying family: #1213, #1228, #1349 (analysis in #1712)

Community Feedback

The maintainer asked for this patch in #1712: "If you have a patch would love to see it and pull it in". #1213/#1228/#1349 contain independent confirmations of the symptom.

Testing

Field-tested on Windows 10 (Ryzen 7 9800X3D, RTX 5070, USB fifine mic, wireless headset + speakers):

  • Before: with Audio Feedback enabled the pipeline hung every 2–30 transcriptions (stuck on "Transcribing…", restart required).
  • After: several days of daily use — hundreds of transcriptions, GigaAM v3 (ONNX) and Whisper Large v3 (Vulkan) — zero hangs.
  • One real audio-stack stall did occur mid-test: it degraded to a few missing chimes with WARN log lines and recovered on its own, which is exactly the designed worst case (one lost chime / one lost utterance, never a stuck app).
  • Verified the "Default" output selection now follows a headphones ↔ speakers default-device switch at runtime.
  • cargo clippy clean on the touched files, cargo fmt applied.

Commit-by-commit rationale and full log forensics are in #1712. The three commits are independent and can be cherry-picked separately if you want only part of this.

I'd appreciate additional testing on macOS/Linux — the recorder changes (bounded waits, command tick) are cross-platform, while the WASAPI-specific observations are Windows-only.

Screenshots/Videos (if applicable)

N/A — sanitized log excerpts are in #1712.

AI Assistance

  • No AI was used in this PR
  • AI was used (please describe below)

If AI was used:

  • Tools used: Claude Code
  • How extensively: root-cause analysis (code reading + log forensics) and the patch code were written with Claude Code under my direction and review; I built the patched app and validated it on my machine over several days of daily use. Commits carry a Co-Authored-By: Claude trailer.

kydechuk01 and others added 5 commits July 18, 2026 15:59
Opening a WASAPI output stream (or enumerating output devices for an
explicit selection) on every chime races concurrent audio session state.
When that call wedges, it takes the rest of the audio stack down with it:
the transcription task blocks inside the microphone stream shutdown and
the pipeline stays "busy" forever (Transcribing... overlay never resolves,
only killing Handy recovers).

Move playback to a dedicated long-lived worker that opens the output
stream once (pre-warmed at startup, while no transcription is running)
and reuses it, recreating only on device change or playback error.
Blocking callers (start-chime -> mute sequencing, test sound) now wait
with a bounded timeout, so even a wedged audio stack cannot stall the
callers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Field testing of the persistent-stream fix surfaced the second half of the
failure chain. When the output stream turns into a zombie after a device
state change (frozen audio clock, no error -- observed with a wireless
headset), the chime worker used to wait on it forever; the wedged WASAPI
state then stopped capture callbacks, and the mic worker only serviced
commands on chunk arrival -- so Stop sat unread and the pipeline hung
"busy" forever even though the chime path was already fire-and-forget.

- mic worker: service commands on a 250ms tick even when the capture
  callback delivers nothing (wedged engine, dead device)
- recorder.stop(): bounded 5s wait -- losing one utterance is recoverable,
  a forever-busy pipeline is not
- recorder.close(): bounded join via reaper thread; a stream drop that
  blocks on a wedged engine parks a disposable thread, not the caller
- chime playback: bounded 5s wait instead of sleep_until_end(); a stalled
  sink marks the stream as zombie
- zombie streams are dropped on throwaway threads and recreated fresh

Worst-case degradation under any WASAPI misbehavior is now one lost chime
or one lost utterance, never a stuck app.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nges

The persistent output stream keeps playing to the device it was opened on,
so after the user switches the Windows default output (headphones <->
speakers) chimes kept going to the old device. Before each use of a
Default-selection stream, resolve what the OS default currently points to
(a cheap device-name query, not a stream creation) and recreate the
stream when it moved. Explicit device selections are unaffected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cjpais

cjpais commented Jul 20, 2026

Copy link
Copy Markdown
Owner

So I'm not super happy with the changes as they were submitted. Mostly they didn't compile which is a pretty bad sign. I'm not sure how this was tested, at least on MacOS this does not compile as submitted

I've reverted the audio feedback related changes. Please give the branch a try in its current state and see if the bug is fixed. I would rather have more targeted changes towards the bug than rewriting things at the moment.

@kydechuk01

Copy link
Copy Markdown
Author

Thanks for reverting the part that didn't build — that one's on me, I only compiled on Windows so I missed that cpal's stream is Send on WASAPI but not on CoreAudio/ALSA.

The recorder changes you kept are the actual fix for the hang. I've been running exactly these on Windows daily for over a week (audio feedback on, hundreds of transcriptions) with zero hangs, where before it was every 2–30. So this branch as-is fixes it for me.

I redid the audio-feedback half properly and cross-platform as a separate PR #1740 (green Linux CI on my fork, and I verified the macOS build compiles and links — it only fails at codesign because my fork has no Apple cert) so this one can stay focused on the recorder fix. Up to you whether it's worth taking.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants