[BUG] Voice push-to-talk regression in v2.1.83: root cause analysis + patch (useEffect dep + EventEmitter stopImmediatePropagation)
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code
What's Wrong?
Voice push-to-talk broke after updating to v2.1.83. Holding Space sends literal space characters into the chat input instead of activating recording. This is a regression — working fine on v2.1.81.
After debugging the minified cli.js, I identified two root cause bugs in the voice hold-detection system (useVoiceKeybindingHandler) and produced a working patch.
---
Root Cause 1 — useEffect dependency array resets hold-detection counter every render
The effect that resets key-repeat counters when leaving "recording" state includes the setState function (Y) in its dependency array. Since its reference can change across renders, the effect re-fires continuously — resetting the repeat counter (M.current) to 0 before it reaches the threshold. The hold gesture is never detected.
// Buggy — Y (setState) in deps triggers on every render
useEffect(() => { if (state !== "recording") M.current = 0; ... }, [state, Y])
// Fixed — remove Y from deps
useEffect(() => { if (state !== "recording") M.current = 0; ... }, [state])
Root Cause 2 — stopImmediatePropagation() is a no-op in Ink's EventEmitter
During recording, the voice handler calls event.stopImmediatePropagation() to prevent space from reaching the text input handler. But Ink uses Node's EventEmitter, not the DOM — stopImmediatePropagation() does nothing. Space keystrokes pass through to the chat input even while recording.
Fix: Replace with an event-consumed flag pattern + prependListener for handler priority:
// Voice handler — register with prependListener so it runs first
useInput((key, mods, event) => {
if (handleVoiceKey(event.keypress)) event._voiceConsumed = true;
}, { isActive: active, priority: "high" })
// In useInput (GD9) — check flag before processing
if (event._voiceConsumed) return;
Supplementary tuning (not root cause, but helps edge cases)
- Hold detection window:
120ms → 800ms - Repeat count threshold:
5 → 2
Verification
Both patches applied to npm package cli.js (v2.1.83), running via node cli.js on macOS ARM64. Push-to-talk fully functional — hold detection works, space no longer floods the input during recording.
Related Issues
- #31341 — original report (3 weeks ago, same symptom)
- #38577 — same regression report (v2.1.83)
- #35156 — Windows variant (marked duplicate)
Steps to Reproduce
- Update to v2.1.83
/voiceto enable voice mode- Hold Space to record
Expected Behavior
Space is captured exclusively by voice mode; microphone records; speech is transcribed on release.
Actual Behavior
- "Listening" indicator appears
- Space simultaneously passes through to chat input
- On release: "no voice detected"
Claude Model
Opus
Is this a regression?
Yes
Last Working Version
2.1.81
Claude Code Version
2.1.83
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
zsh
This issue has 6 comments on GitHub. Read the full discussion on GitHub ↗