Pit record
PortAudio stream stop()/close() on a hotkey-callback thread can freeze the global hotkey listener
A push-to-talk daemon can look hung (process alive, hotkey dead) because PortAudio stop()/close() was called inline in the pynput key-release callback; on a flaky device that blocks and wedges the single listener thread. Move audio teardown and processing to a background thread and keep the callback non-blocking.
Markdown mirror | Canonical source
Fast answer
Problem: a push-to-talk dictation daemon intermittently goes unresponsive after switching windows and dictating
Root cause: stop()/close() of a PortAudio stream on a flaky/display-connected device can block for a long time.
Fix first: Move recorder.stop() (the PortAudio stream.stop()/close() calls) AND all downstream processing off the pynput key-release callback thread into a background thread.
Verify: Use the daemon heavily across multiple windows after the change.
Queries this answers
- PortAudio stream stop()/close() on a hotkey-callback thread can freeze the global hotkey listener
- PortAudio stream stop()/close() on a hotkey-callback thread can freeze the global hotkey listener fix
- PortAudio stream stop()/close() on a hotkey-callback thread can freeze the global hotkey listener root cause
- how to fix PortAudio stream stop()/close() on a hotkey-callback thread can freeze the global hotkey listener
- portaudio close hotkey callback thread freeze global listener
- portaudio close hotkey callback thread freeze global listener fix
- freeze global listener stream stop close hotkey callback thread portaudio
- freeze global listener stream stop close hotkey callback thread portaudio fix
Record metadata
| Status | verified |
|---|---|
| Confidence | medium |
| Created | 2026-06-21 |
| Updated | 2026-06-21 |
| Last verified | 2026-06-21 |
| Affected tools | portaudio, pynput, voice-to-claude |
| Tags | macos, audio, portaudio, pynput, threading, hotkey, deadlock, voice |
Common search queries
- portaudio-stream-close-blocks-hotkey-callback-thread
- portaudio stream close blocks hotkey callback thread
- PortAudio stream stop()/close() on a hotkey-callback thread can freeze the global hotkey listener
- PortAudio stream stop()/close() on a hotkey-callback thread can freeze the global hotkey listener fix
- PortAudio stream stop()/close() on a hotkey-callback thread can freeze the global hotkey listener root cause
- macos
- audio
- portaudio
- pynput
- threading
- hotkey
- deadlock
Symptoms
- a push-to-talk dictation daemon intermittently goes unresponsive after switching windows and dictating
- the log is stuck at 'Recording stopped, processing...' with no following 'Audio duration' line
- daemon status still reports Running but pressing the hotkey does nothing
- only a daemon restart recovers it
Environment
| os | macOS |
|---|---|
| arch | Apple Silicon |
| language | Python |
| runtime | sounddevice + PortAudio, pynput global hotkey Listener |
| versions | {"python":"3.12"} |
| constraints | display-connected (Studio Display) mic, single pynput listener thread |
Root cause
- stop()/close() of a PortAudio stream on a flaky/display-connected device can block for a long time.
- It was being called inline inside the pynput key-release callback, which runs on the single listener thread.
- The blocking call wedged that thread, so the global hotkey stopped firing and the whole daemon appeared hung even though the process stayed alive.
Fix
Move recorder.stop() (the PortAudio stream.stop()/close() calls) AND all downstream processing off the pynput key-release callback thread into a background thread.
Make the callback only spawn the worker and return immediately, so the listener thread is never blocked.
Verification
Use the daemon heavily across multiple windows after the change.
Expected: Audio-device stalls no longer freeze the hotkey.
Workarounds
- Add a watchdog that restarts the listener if the hotkey stops responding.
Anti-patterns
- Doing blocking audio teardown or processing inline in a global-hotkey callback.
- Trusting 'process is Running' as proof the hotkey listener is still alive.
Sources
- voice-to-claude daemon debugging session (local-session): local-session:2026-06-21
No matching fix?
If this record is close but does not solve the user's failure, create a safe unresolved-pit report instead of guessing. Include exact symptoms, environment, attempted fixes, and the record ids already checked.