[Backend] Drop all-silence tracks before transcription #348
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Severity: MEDIUM. Found in the August 2026 session lifecycle review (#319). New failure mode created by the capture fix.
The defect
Once capture fills gaps with silence, a user who joins voice but never actually speaks — or whose client emits only DTX comfort-noise frames — produces a full-length, entirely silent track. That track is then sent to Whisper, which is well documented to hallucinate repeated phantom text on silence. Because attribution is per-track, those phantom lines are attributed to that user.
That is an independent misattribution vector, arriving as a direct consequence of fixing the first one.
There is a related trap already in the code:
_peak_amplitude(recording.py:243-254) reads only the first 0.5 s of a file, which is guaranteed silence for every padded speaker. Any check built on it reports zero for every non-first speaker and would mislead exactly this investigation.Proposed fix
At
close(), scan the whole file (not the first half second) for peak amplitude and total non-silent duration. Drop tracks with no real speech before upload, and record them in the existinguncaptured_member_idsfield so the GM sees "present, never captured" rather than a phantom transcript.Add a backend-side guard too, since the bot cannot be the only line of defence: a track whose speech duration is effectively zero should not be sent for transcription.
Acceptance criteria
_peak_amplitudescans the full file, not the first 0.5 sVerified against acceptance criteria. Four met, one unmet-as-worded but superseded. The gap that mattered was the fourth, and it was fixed in
540b0a2under #425.Criteria
_peak_amplitudescans the full file, not the first 0.5 s — unmet as worded, and deliberately so. It still samples five 64 KB windows (recording.py:374-400).But the criterion was written when
_peak_amplitudewas going to be the drop decision. It isn't. The decision now usesspeech_bytes_written()(recording.py:887) — a running counter of decoded audio accumulated during capture, with no sampling blind spot at all._peak_amplitudesurvives only as a log diagnostic, and its docstring says so and explains why it samples spread through the file rather than at the head.Making it scan exhaustively now would add cost to a log line. The intent behind the criterion — "do not decide this from a head-only read" — is satisfied by something stronger than what was asked for. Recording it as superseded rather than ticking it.
All-silence tracks are dropped before upload and reported as uncaptured — recording.py:969-978, merged into
uncaptured_member_idsand sent viaapi_client.post_audio_tracks.The backend independently skips tracks with no detectable speech —
drop_silent_tracks, called unconditionally inprocess_audio. Its detection was rewritten under #425 after the original sampling version turned out to drop quiet players from long sessions.→ [x] The GM can see which participants were present but never captured — was met for bot-dropped tracks only. The backend's own drops went to a
logger.infoand never reacheduncaptured_member_ids, which was built solely frompresence.json. Since that file carries only what the bot declined to upload, a track dropped by the backend guard was invisible to the GM. Fixed in540b0a2; full reasoning on #425.Tests cover a silence-only track and a track with one brief utterance — test_silent_tracks.py, and the brief-utterance case is now tested at realistic session length (4,639 s) with speech scattered, not contiguous. I confirmed those fail against the pre-#425 sampler rather than trusting their docstrings.
Worth noting
This issue's own body contains the warning that caught #425: "
_peak_amplitudereads only the first 0.5 s of a file... Any check built on it reports zero for every non-first speaker and would mislead exactly this investigation." The backend guard then shipped with a different version of the same defect — sampling that degrades with file length instead of position — and the test written to prove otherwise used a 60-second fixture.Closing.