Repository navigation
Keep meeting system audio alive across repeated sleep/wake and output changes - #1762
Merged
Merged
Conversation
Every system wake reconnected the process tap through the stall recovery path, which allows one reconnect per recording. The second wake in a meeting found that budget spent and ended system audio for the rest of the recording while the mic kept going. Wake reconnects no longer spend or need the stall budget. A reconnect that is still waiting for its first buffer is released before the next one arms, so the write-hold stays balanced and the silence pad still covers the whole interruption. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015njvTLqC9Fao3TMUNouKka
7 of 9 tasks
Hardware test 2026-09-23 (two sleeps in one meeting): each sleep entry
stopped tap buffers for 3s. The first stall spent the one stall
reconnect; the second hit the spent budget and failed the tap before the
wake could reconnect it. The wake reconnect then returned early because
the tap was no longer running, so system audio stayed dead for the rest
of the meeting.
The failure message ("...no audio buffers after reconnecting") also
contains "reconnecting", so the status stayed on reconnecting and
system_failed stayed false. That hid the dead tap.
- Audio tells the system-audio backend when the Mac is about to sleep.
The process tap skips stall recovery until the wake reconnect, which
clears the hold. Only awake time counts toward a 30s safety limit, so
a sleep that never wakes can't disable stall recovery.
- "System audio failed" wins over "reconnecting" when mapping backend
messages to status.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015njvTLqC9Fao3TMUNouKka
9 of 11 tasks
A code audit found that any change to the process tap's format ended system
audio for the rest of the meeting. The format listener flagged it, drain
called fail("...start a new recording"), and recovery could not help
because acceptFormat refused any new format. 1.1.61's ScreenCaptureKit path
resampled for us, so this is new in 1.1.62. Likely triggers are AirPods
switching to their call profile, or plugging in or switching an output
device mid-meeting.
On a format change the tap is now rebuilt (without spending the stall
reconnect, and without the "reconnecting" warning) and resampled back to
the recording's first format with AVAudioConverter on the consumer queue,
so the host's WAV keeps one rate. A route that keeps flapping still ends
cleanly after five rebuilds.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015njvTLqC9Fao3TMUNouKka
This was referenced Sep 23, 2026
This was referenced Sep 23, 2026
claude Bot
pushed a commit
that referenced
this pull request
Sep 23, 2026
- Call-audio tap (#1762/#1771): per-recording reconnect counts by cause (wake, format change, silent after wake, stall), rebuild retries, sleeps, silent-after-wake give-ups and why system audio ended. Bucketed on meeting events and allowlisted as Sentry tags. - #1767: mic_format_rebuilds_bucket when AirPods flip format at start. - #1768: meeting_system_audio_prompt_answered (outcome, tcc_status), plus mic_only_by_choice and system_permission_check on meeting_recording_started. - #1773: launch_models_warmed once per launch; models_warm on meeting_recording_started. - #1774: headset_mic_switched on dictation events. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SRK9LR6bS7HMphU19DMDfy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Justin · project thread
Why
Before: if the Mac slept twice during one meeting, system audio stopped for the rest of the recording. The same happened if the output device changed its sample rate mid-meeting, for example AirPods switching into call mode or a new output device. The mic kept going, so the transcript silently lost the other side of the call. Both are regressions from 1.1.61, because they come from the new Core Audio process tap.
After: system audio comes back after every wake. When the output rate changes, the tap is rebuilt and resampled to the recording's original rate, so the WAV keeps one rate. Stalls still get exactly one reconnect per recording, and a wake, falling asleep or a format change doesn't use it up.
Three causes, all in
CoreAudioSystemAudioCapture:AudiokicksrecoverAfterSystemWake()on every wake, and that went throughrecover(), which spends the one-per-recordingrecoveryUsedbudget.Audiotells the backend when the Mac is about to sleep, and the tap holds stall recovery until the wake reconnect.fail("…start a new recording"), andacceptFormatrefused any new format even inside recovery. 1.1.61's ScreenCaptureKit path resampled for us.There was also a status bug that hid the dead tap. "System audio failed - no audio buffers after reconnecting." contains "reconnecting", so it mapped to
.reconnecting. A "System audio failed" message now wins.Hardware: two real sleeps passed on Justin's Mac with causes 1 and 2 fixed. Cause 3 is not yet exercised on hardware.
Product Impact
meetingsmeeting reliabilityWhat changed
CoreAudioSystemAudioCapture:recover(_ trigger:)handles.stall,.systemWakeand.formatChange. Only a stall spends the one reconnect and shows "reconnecting". A reconnect that starts while another is still waiting for its first buffer releases the earlier one with.recoveryAbandoned, soAudio's write-hold stays balanced.prepareForSystemSleep(): stall recovery waits from will-sleep until the wake reconnect. That hold ends after 30s of awake time.formatis the recording's first format, andtapFormatis the live tap's. On a change,acceptFormatbuilds anAVAudioConverterandconvertToRecordingFormatresamples on the consumer queue, never the IO proc. At most 5 format rebuilds per recording (maxFormatReconnects), then it fails cleanly.SystemAudioCaptureEngine.prepareForSystemSleep()is a new hook with a no-op default, and theAudiowill-sleep observer calls it.Audio.updateSystemAudioStatus: "system audio failed" is checked before "reconnecting".testEverySystemWakeGetsItsOwnReconnect,testWakeReconnectLeavesStallBudgetForALaterStall,testWakeDuringPendingReconnectKeepsWriteHoldBalanced,testSilenceWhileTheMacFallsAsleepKeepsTheStallReconnect,testSleepThatNeverWakesStopsHoldingStallRecovery,testSleepNoticeAfterStopIsIgnored.testFormatInvalidationDiscardsQueuedOldFormatAndReconnectsQuietly,testOutputRateChangeIsResampledToTheRecordingFormat,testAFlappingRouteStillEndsCleanly,testChangedFormatOnWakeIsKeptAtTheRecordingRate. The last two replace the old "format change terminates" and "changed recovery format is rejected" tests.testUpdateSystemAudioStatusTreatsAFailureAfterReconnectingAsFailed.How I checked it
scripts/dev/agent-preflight.shpython3 scripts/dev/check-build-source-lists.pybash build.sh --no-open/bash run-tests.sh/bash run-integration-smoke.sh/swift test: not run locally (no Swift toolchain in this session). CI is the compile.Risk Review
Real-time safety: the IO proc and ring are untouched. Conversion and everything else changed runs on the capture's serial
queue, or on main for the status mapping.Notes
🤖 Generated with Claude Code
https://claude.ai/code/session_015njvTLqC9Fao3TMUNouKka