Voice mode: capture doesn't re-acquire the input device on default-device or topology changes — Bluetooth connect/disconnect silently kills dictation until restart
Environment
- Claude Code 2.1.239, macOS (Darwin 25.3.0), iTerm2
- Mics: Shure MV7+ (USB), AirPods Pro (Bluetooth)
- Voice mode: tap (
/voice tap); hold mode behaves the same
Summary
Voice mode resolves the audio input device once (at process start, or first
capture) and never re-acquires it. Two distinct failure classes follow, both
reproduced and isolated with OS-level capture verified healthy each time:
- Default-input change is ignored. Started a session while AirPods were
the system default input, then switched the default to the USB mic in
System Settings. Voice mode kept recording from the old device. Toggling
/voice off/on does not rebind; only restarting the CLI does.
- Device-topology change wedges capture entirely. With the correct mic
already the default and working, disconnecting the AirPods (removing
them) killed voice capture — even though the *pinned default device never
changed*. The AirPods leaving reshuffles the Core Audio device graph, the
stale stream dies, and voice mode delivers silence until the CLI is
restarted. Reconnecting Bluetooth devices does the same. In practice every
AirPods connect/disconnect during a session costs a restart.
Why it looks like a user problem (and why it isn't)
The failure is silent: no error is surfaced at tap time (eventually "Voice
input is failing repeatedly and has been paused"). During each incident we
verified the OS side independently:
SwitchAudioSource -t input -cconfirmed the correct default input.AVCaptureDevice.authorizationStatus(for: .audio)returnedauthorized
for the hosting terminal.
ffmpeg -f avfoundation -i ":<mic>" -t 4 -af volumedetectcaptured
healthy live audio (mean approx. -28 dB, peaks approx. -10 dB) from the exact device
voice mode should be using — at the same moment voice mode produced
nothing.
A fresh claude process picked the mic up correctly every time, which
isolates the defect to the running process's device binding.
Expected behavior
Standard macOS capture hygiene: subscribe to the default-input-device-changed
property (kAudioHardwarePropertyDefaultInputDevice) and to configuration-
change notifications (AVAudioEngineConfigurationChange or equivalent), and
rebuild/re-acquire the input on either. Most capture apps (Zoom, QuickTime)
survive AirPods churn transparently.
Suggested behavior if a full fix is deferred
Even without live rebinding, detecting a dead/invalidated stream and surfacing
"input device changed — voice needs a restart" (or auto-rebinding on the next
tap) would convert a silent failure into an actionable one.
Related
- #36041 (input-device selection feature request) — complementary: a device
picker doesn't help if the binding to the picked device goes stale.