Recover the mic when the audio device changes mid-session - #3
Open
CansuKhon wants to merge 1 commit into
Open
Conversation
PortAudio caches the device list when it initializes. A Bluetooth headset flipping between listen mode and mic mode (AirPods do this every time the mic opens) invalidates the cached device, and every capture after that fails with -9986 until the process restarts. The failure is quiet in the worst way: the voice line stays up, the greeting still plays, the key still registers, and the agent answers "my ears hit an error" on every single press. Add _input_stream(), which re-initializes PortAudio and retries once on PortAudioError. Both capture paths use it: record_held for push-to-talk and Ears.listen_once for the open mic. The _terminate() call is guarded because it raises if PortAudio is already down. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
On macOS with Bluetooth headphones (AirPods Max here), the voice line transcribes
fine for the first several turns, then every recording after that fails:
Once it starts, it never recovers — every subsequent press fails the same way until
the process is restarted.
Why
PortAudio caches the device list when it initializes. A Bluetooth headset flips
between listen mode and mic mode every time the mic opens, which invalidates the
cached device.
record_heldopens a freshInputStreamper press, so every openafter the switch hits a device that no longer exists as PortAudio remembers it.
The failure mode is quiet in the worst way: the voice line stays up, the greeting
plays, the key registers,
[ptt] recordingstill prints — and the agent answers"my ears hit an error" on every single press. Everything looks healthy.
The fix
_input_stream()re-initializes PortAudio (which refreshes the device list) andretries once on
PortAudioError. Both capture paths use it —record_heldforpush-to-talk and
Ears.listen_oncefor the open mic.The
_terminate()call is guarded: it raisesPortAudioErrorif PortAudio isalready down, which would otherwise turn the recovery path into a second crash.
(Found that one by testing the recovery, not by reading it.)
Testing
On macOS 15 (darwin 25.6.0), Python 3.12, sounddevice 0.5.6:
sd._terminate()to reproduce the -9986 state),then
_input_stream()— recovers, logs[ears] audio device changed — reopening the mic.record_held()through a deliberately broken device — recovers and returnstranscribed text.
Found while setting up fullstack-agent. Happy to adjust the approach if you'd
rather handle it at a different layer.
🤖 Generated with Claude Code