Voice transcription routes to main input instead of active dialog/reply box
Bug Description
When voice mode is enabled and a secondary input field is focused (e.g., plan mode reply box, AskUserQuestion dialog), holding Space triggers voice recording correctly — but the transcribed text appears in the main prompt input instead of the dialog's reply box.
After pressing Escape to dismiss the dialog, the transcribed text is visible in the main input underneath.
Steps to Reproduce
- Start Claude Code with voice mode enabled (
/voice) - Trigger plan mode or any action that shows an
AskUserQuestiondialog (the reply box with accept/reject/clear context/reply options) - With the dialog reply box focused, hold Space to record voice
- Speak a message, release Space
- Observe: The transcribed text does NOT appear in the dialog's reply box
- Press Escape to dismiss the dialog
- Observe: The transcribed text appears in the main prompt input below
Expected Behavior
When a dialog/reply box is focused and the user holds Space for voice input, the transcribed text should be inserted into the currently focused input (the dialog's reply box), not the main prompt.
Root Cause Analysis
Traced through the minified bundle (cli.js v2.1.69) across the full code path:
The complete chain:
- Space held → detected at process/module level (NOT via Ink's
useInputwithisActiveguards) Cvq()starts recording (line ~5895) — checks only theAk6boolean flag, has zero modal/focus checks- Audio captured via sox/native module
- Space released →
Ivq()stops recording, audio sent for transcription onTranscriptcallback fires (line ~12904) → callsH.current(text)H.currentis a stale ref — initialized once viauseRef, always points to the main prompt'sonInputhandler, never updated when focus moves to a dialogwrappedOnInput(line ~3620) receives text → callsq(D, X)→ updates main prompt input state- Dialog's input handler (registered via separate
useInputwith its owninputValuestate) never receives the transcription
Three independent failures:
| # | Failure Point | Detail |
|---|---------------|--------|
| 1 | No focus/modal check in Cvq() | Recording starts unconditionally — no check for questionState.isActive or dialog focus |
| 2 | Stale H.current ref | Voice hook stores the input handler ref once at init, never updates when focus moves to dialog input |
| 3 | No questionState awareness | Voice system has no concept of dialog/modal input contexts |
Key code references (minified names):
Cvq()— startRecording function (~line 5895)Ak6— global recording state booleanH.current— stale useRef holding main prompt's onInput (~line 12904)wrappedOnInputinGwq— input routing function (~line 3620)questionState— dialog active state (11 occurrences, not checked by voice)
Suggested Fix
Either:
- Update
H.currentwhenever input focus changes (so it always points to the active input's handler), OR - Add focus/modal gating in the voice handler — check
questionState.isActivebefore recording and route transcription to the dialog's input handler when active, OR - Use Ink's
isActiveparameter on the voiceuseInputhook so it's automatically disabled when a dialog has focus priority
Environment
- Claude Code version: 2.1.68
- OS: macOS (Darwin 25.3.0, arm64)
- Installation: Homebrew
Related
- #30968 (voice mode doesn't auto-activate on session start)
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗