A capture that is silent for its whole duration is currently transcribed like any other. It should be surfaced as a condition, because the two things that produce it are both real and both invisible from the loop's output.
The two sources
A hardware mute. The Anker PowerConf has a capacitive mute that gates the stream with no Linux-level signal — flagged as an operational cost in #36. Nothing in the capture path can tell a muted mic from a quiet room.
A dead or wrong device. A device that opens successfully and delivers frames of nothing, which is the shape a disconnected or misrouted input takes when the stream itself is healthy.
Both present identically: capture_request runs, returns frames, transcription produces nothing or hallucinated filler, and the loop reports no error. The operator sees an assistant that stopped responding, with no indication of which layer to look at.
This is the same diagnostic gap 394a805 closed for narrowband capture, and the reasoning there applies unchanged: the failure is invisible at every stage before the transcript, so the mic is the last thing suspected. That warning cost an afternoon to earn. This is the adjacent case, where the audio is not degraded but absent.
Why it is worth its own check
Energy endpointing cannot distinguish these, and adding the case to endpointing would be the wrong place. pipeline.py:686-690 counts frames below _SILENCE_RMS to find the trailing edge of speech. An all-silent capture is not a short utterance, it is the absence of one, and treating it as an endpoint just yields an empty transcript faster.
It also sits on the other side of #35. That bug is over-capture — sustained noise resets the quiet counter so a capture never ends and runs to the 8-second cap. This is under-capture, where every frame is quiet. Same blind spot in the same signal, opposite directions, and one condition check covers this half.
Shape
Cheap, and hardware-free in the same way capture_quality.py already is:
- After a capture completes, if every frame is below
_SILENCE_RMS, do not transcribe it.
- Report it as a distinct condition rather than an empty result — the message should name what to check (mute button, device selection) rather than only stating that nothing was heard.
- The threshold already exists and is already the pipeline's notion of "not speech," so no new tuning constant.
Worth being deliberate about one edge: a genuine wake with no follow-up speech is already handled by _NO_SPEECH_ONSET_FRAMES, so this should apply to the captured-request path and not double-report the abandoned-wake case.
Testable without a microphone
An array of zeros or low-amplitude noise is enough to exercise it, matching the hardware-free-core split the project already draws and test_capture_quality.py's existing approach.
Split out of #36 rather than folded in, since that issue is a mic comparison and this is pipeline reliability that pays off regardless of which mic wins.
wake-20260730T0805-f4f8fd
A capture that is silent for its whole duration is currently transcribed like any other. It should be surfaced as a condition, because the two things that produce it are both real and both invisible from the loop's output.
The two sources
A hardware mute. The Anker PowerConf has a capacitive mute that gates the stream with no Linux-level signal — flagged as an operational cost in #36. Nothing in the capture path can tell a muted mic from a quiet room.
A dead or wrong device. A device that opens successfully and delivers frames of nothing, which is the shape a disconnected or misrouted input takes when the stream itself is healthy.
Both present identically:
capture_requestruns, returns frames, transcription produces nothing or hallucinated filler, and the loop reports no error. The operator sees an assistant that stopped responding, with no indication of which layer to look at.This is the same diagnostic gap
394a805closed for narrowband capture, and the reasoning there applies unchanged: the failure is invisible at every stage before the transcript, so the mic is the last thing suspected. That warning cost an afternoon to earn. This is the adjacent case, where the audio is not degraded but absent.Why it is worth its own check
Energy endpointing cannot distinguish these, and adding the case to endpointing would be the wrong place.
pipeline.py:686-690counts frames below_SILENCE_RMSto find the trailing edge of speech. An all-silent capture is not a short utterance, it is the absence of one, and treating it as an endpoint just yields an empty transcript faster.It also sits on the other side of #35. That bug is over-capture — sustained noise resets the quiet counter so a capture never ends and runs to the 8-second cap. This is under-capture, where every frame is quiet. Same blind spot in the same signal, opposite directions, and one condition check covers this half.
Shape
Cheap, and hardware-free in the same way
capture_quality.pyalready is:_SILENCE_RMS, do not transcribe it.Worth being deliberate about one edge: a genuine wake with no follow-up speech is already handled by
_NO_SPEECH_ONSET_FRAMES, so this should apply to the captured-request path and not double-report the abandoned-wake case.Testable without a microphone
An array of zeros or low-amplitude noise is enough to exercise it, matching the hardware-free-core split the project already draws and
test_capture_quality.py's existing approach.Split out of #36 rather than folded in, since that issue is a mic comparison and this is pipeline reliability that pays off regardless of which mic wins.
wake-20260730T0805-f4f8fd