You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[bug] #490 added three paths that discard captured audio with no report — the idle recycle eats the clip and the live words, and a backgrounded tab discards every clip #510
#490 added three paths that throw audio away with no report — the invariant #377/#420/#425 established
The owner: "sometimes it wasn't capturing or transcribing my voice even." These are the paths that can do that and leave no trace, so neither he nor the error log can tell it happened.
1. The idle recycle discards the clip AND the live words, silently
No gate consultation, no reportClientError, no bubble left behind. Compare the "end" branch thirteen lines above (use-voice.ts:455-458), which asks endOfTurnAction(gateSnapshot()) before discarding. The two paths disagree about whether a proven-alive Web Speech gate may vouch for the turn — and the idle path is the one that throws away both the audio and the words the user is watching appear on screen.
vadStep returns "idle" after 15 s when onset never fires (vad.ts:196-198; the caller passes no idleMs, so the 15 000 default applies — use-voice.ts:446).
User-visible signature: the pill pulses "Listening", live words appear, and 15 s later everything vanishes and the mic silently reopens. Saying it again usually works, because the VAD state is fresh. That is verbatim the #377 failure shape ("watched their own words appear and then vanish"), live again on this branch.
It also cannot produce a watchdog row: clearVoiceText() runs beforestopDiscard(), so no turn is left in transcribing and the 50 s watchdog (use-voice.ts:70, :1803) never arms. There are zero(watchdog) rows in the last 83 error rows — consistent with this path, and by construction it could never appear there.
2. _peakLevel === 0 now discards — and a backgrounded tab guarantees it
packages/sdk/src/voice/stt.ts:364-372
if(this._peakLevel===0){// No energy data at all. Discard rather than uploading an unknown clip.this.onEnd();return;}
The comment #490 deleted stated the old contract: "Skipped when nothing ever called noteLevel(), so a caller with no analyser keeps working rather than going silently deaf."#490 inverted exactly that.
Two problems:
The comment above the branch claims the discard happens "AND there is no Web Speech gate available to vouch for the turn". There is no gate check in the branch. The comment describes a design the code does not implement.
#377, #420 and #425 are all closed, and together they established the invariant that every drop path reports itself. #490 broke it in three places. #291 ("Stop swallowing runtime errors across voice, coder, and worker paths") is open and is exactly this subject — but both paths above postdate its last pass (50bea16, "the last 29 swallowed failures"), so they are uncounted by it.
Acceptance criteria
The idle-recycle path consults the gate the same way the "end" path does, or states in code why it must not.
Every path that discards captured audio reports itself to the durable log — including the idle recycle and the zero-peak discard.
The zero-peak branch either implements the gate check its comment describes, or the comment is corrected to match the code.
Voice error rows carry enough context to diagnose from the log alone: at minimum peakLevel, noiseFloor, onsetFloor and whether the gate heard speech. Today context is only {code} / {transcript,path} / {sttWhisper}, which is why this took a code read rather than a query.
A test for the idle path asserting it reports, and one for a zero-peak clip with a live gate.
#490 added three paths that throw audio away with no report — the invariant #377/#420/#425 established
The owner: "sometimes it wasn't capturing or transcribing my voice even." These are the paths that can do that and leave no trace, so neither he nor the error log can tell it happened.
1. The idle recycle discards the clip AND the live words, silently
packages/sdk/src/voice/use-voice.ts:468-475No gate consultation, no
reportClientError, no bubble left behind. Compare the"end"branch thirteen lines above (use-voice.ts:455-458), which asksendOfTurnAction(gateSnapshot())before discarding. The two paths disagree about whether a proven-alive Web Speech gate may vouch for the turn — and the idle path is the one that throws away both the audio and the words the user is watching appear on screen.vadStepreturns"idle"after 15 s when onset never fires (vad.ts:196-198; the caller passes noidleMs, so the 15 000 default applies —use-voice.ts:446).User-visible signature: the pill pulses "Listening", live words appear, and 15 s later everything vanishes and the mic silently reopens. Saying it again usually works, because the VAD state is fresh. That is verbatim the #377 failure shape ("watched their own words appear and then vanish"), live again on this branch.
It also cannot produce a watchdog row:
clearVoiceText()runs beforestopDiscard(), so no turn is left intranscribingand the 50 s watchdog (use-voice.ts:70,:1803) never arms. There are zero(watchdog)rows in the last 83 error rows — consistent with this path, and by construction it could never appear there.2.
_peakLevel === 0now discards — and a backgrounded tab guarantees itpackages/sdk/src/voice/stt.ts:364-372The comment #490 deleted stated the old contract: "Skipped when nothing ever called
noteLevel(), so a caller with no analyser keeps working rather than going silently deaf." #490 inverted exactly that.Two problems:
noteLevelhas exactly one caller — therequestAnimationFrametick atuse-voice.ts:427. rAF callbacks do not run for a document that is notvisible. With the console tab backgrounded or the window minimised — entirely normal for someone talking to a coding agent while watching a terminal —_peakLevelstays 0, the 60 s max-dictation cap fires (use-voice.ts:779-788), and every clip is discarded. Before [bug] STT transcribes silence in hands-free mode — Whisper hallucinations posted as phantom user turns ("Pottery Barn", "Thank you for watching") — gate fails on iOS Safari and when SpeechRecognition stalls #490 that clip transcribed and sent. Thevisibilitychangehandler (use-voice.ts:1835-1843) only restarts on becoming visible; nothing stops the loop while hidden.Inferred, not observed: no live row proves a hidden-tab discard occurred — and by construction there cannot be one. That is the point.
Why this belongs with #291
#377, #420 and #425 are all closed, and together they established the invariant that every drop path reports itself. #490 broke it in three places. #291 ("Stop swallowing runtime errors across voice, coder, and worker paths") is open and is exactly this subject — but both paths above postdate its last pass (
50bea16, "the last 29 swallowed failures"), so they are uncounted by it.Acceptance criteria
"end"path does, or states in code why it must not.noteLevelgets a source that runs while hidden, or the zero-peak case is treated as "no analyser data" (the pre-[bug] STT transcribes silence in hands-free mode — Whisper hallucinations posted as phantom user turns ("Pottery Barn", "Thank you for watching") — gate fails on iOS Safari and when SpeechRecognition stalls #490 contract) rather than "no speech".peakLevel,noiseFloor,onsetFloorand whether the gate heard speech. Todaycontextis only{code}/{transcript,path}/{sttWhisper}, which is why this took a code read rather than a query.