Skip to content

fix(desktop): recover dead mic on buffered PTT silent turns (#9081)#9390

Open
kodjima33 wants to merge 1 commit into
mainfrom
watchdog/issue-9081-ptt-silent-turn-recovery
Open

fix(desktop): recover dead mic on buffered PTT silent turns (#9081)#9390
kodjima33 wants to merge 1 commit into
mainfrom
watchdog/issue-9081-ptt-silent-turn-recovery

Conversation

@kodjima33

@kodjima33 kodjima33 commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Bug (#9081)

Desktop telemetry showed ptt_audio_capture_silent_turn with recovery_action=none / recovery_result=not_attempted for 169 users on 0.12.0 in hold mode — push-to-talk turns recorded silence and nothing was done to recover the mic.

Root cause

There are four silent-turn discard sites in PushToTalkManager. The two primary gates (finalize() hub path and the omni/batch path) already run silentMicRecoveryPolicy.recordDiscardedTurn(...) and, once consecutive dead-mic turns cross the threshold, call requestCoreAudioCaptureRecovery(...) to rebuild CoreAudio capture.

The two buffered exits did not:

  • commitBufferedRealtimeHubTurn()source: "buffered_hub"
  • transcribeBufferedWarmWaitAudio()source: "warm_wait_fallback"

Both are taken when the user releases before the realtime hub has finished warming — the common hold-mode path. They discarded near-silent turns and passed no recovery args, so recordPTTSilentTurn fell back to its recoveryAction: "none" default and the dead-mic counter never advanced. A user stuck on this path could never trip recovery.

Fix

Wire both buffered paths into the exact same dead-mic recovery the primary paths use:

  • count each discard via recordDiscardedTurn,
  • attempt a capture rebuild once the threshold trips,
  • emit the real recovery_action/recovery_result,
  • reset the counter on the buffered success paths so a recovered mic clears accumulated dead turns.

Surgical, mirrors the existing sibling pattern. additions+deletions = 80 across 1 source file + 1 test + 1 changelog.

Test

PTTSilentTurnRecoveryWiringTests — since PushToTalkManager is a @MainActor singleton not constructible in a unit test, this follows the repo's existing source-scrape pattern (PTTAudioCaptureRaceTests): asserts every recordPTTSilentTurn site wires recovery, so a future unwired site fails CI.

Verification

  • xcrun swift build -c debug --package-path DesktopBuild complete! (177s).
  • UNVERIFIED at runtime: could not reproduce a physical dead-mic + hub-warm-wait turn end-to-end.

🤖 automated by hourly watchdog; opened for review, not merged.

Review in cubic

The two buffered push-to-talk silent-turn exits — commitBufferedRealtimeHubTurn
(source "buffered_hub") and transcribeBufferedWarmWaitAudio ("warm_wait_fallback"),
both hit when the realtime hub is still warming at release — discarded near-silent
turns without ever feeding silentMicRecoveryPolicy.recordDiscardedTurn or requesting
a CoreAudio capture rebuild. The primary hub/omni/batch gates already do this. A
user whose hold-mode turns landed on the warm-wait path could accumulate silent
turns forever with recovery_action=none / recovery_result=not_attempted — exactly
the telemetry in #9081 (169 users, 0.12.0, hold mode).

Fix: wire both buffered paths into the same dead-mic recovery the primary paths use
— count each discard toward the threshold, attempt a capture rebuild once tripped,
and emit the real recovery_action/recovery_result. Also reset the counter on the
buffered success paths so a recovered mic clears accumulated dead turns.

Regression test (source-scrape, matching PTTAudioCaptureRaceTests since the manager
is a @mainactor singleton): every recordPTTSilentTurn site must wire recovery.

Verification: xcrun swift build -c debug succeeded (Build complete!, 177s).
UNVERIFIED at runtime: could not reproduce a physical dead-mic + hub-warm-wait turn.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant