Skip to content

Keep meeting system audio alive across repeated sleep/wake and output changes - #1762

Merged
claude[bot] merged 3 commits into
mainfrom
claude/fix-system-audio-second-wake
Sep 23, 2026
Merged

claude[bot] merged 3 commits into
mainfrom
claude/fix-system-audio-second-wake

Conversation

@claude

@claude claude Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

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:

  1. Wakes spent the stall budget. Audio kicks recoverAfterSystemWake() on every wake, and that went through recover(), which spends the one-per-recording recoveryUsed budget.
  2. Sleep entry spent it too. Hardware test 2026-09-23: the tap stops delivering buffers as the Mac falls asleep, and three seconds later the stall watchdog reconnects. On the second sleep the stall hit the spent budget and failed the tap, then the wake reconnect returned early. Now Audio tells the backend when the Mac is about to sleep, and the tap holds stall recovery until the wake reconnect.
  3. Any tap format change was fatal. A code audit (2026-09-23) found the format listener flagged the change, drain called fail("…start a new recording"), and acceptFormat refused 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

  • Affects: meetings
  • Lane: meeting reliability
  • Why this matters: closing the lid or switching to AirPods mid-meeting is normal, and it shouldn't cost the other half of the transcript.

What changed

  • CoreAudioSystemAudioCapture:
    • recover(_ trigger:) handles .stall, .systemWake and .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, so Audio's write-hold stays balanced.
    • prepareForSystemSleep(): stall recovery waits from will-sleep until the wake reconnect. That hold ends after 30s of awake time.
    • format is the recording's first format, and tapFormat is the live tap's. On a change, acceptFormat builds an AVAudioConverter and convertToRecordingFormat resamples 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 the Audio will-sleep observer calls it.
  • Audio.updateSystemAudioStatus: "system audio failed" is checked before "reconnecting".
  • Tests:
    • Sleep and wake: testEverySystemWakeGetsItsOwnReconnect, testWakeReconnectLeavesStallBudgetForALaterStall, testWakeDuringPendingReconnectKeepsWriteHoldBalanced, testSilenceWhileTheMacFallsAsleepKeepsTheStallReconnect, testSleepThatNeverWakesStopsHoldingStallRecovery, testSleepNoticeAfterStopIsIgnored.
    • Format changes: testFormatInvalidationDiscardsQueuedOldFormatAndReconnectsQuietly, testOutputRateChangeIsResampledToTheRecordingFormat, testAFlappingRouteStillEndsCleanly, testChangedFormatOnWakeIsKeptAtTheRecordingRate. The last two replace the old "format change terminates" and "changed recovery format is rejected" tests.
    • Status: testUpdateSystemAudioStatusTreatsAFailureAfterReconnectingAsFailed.

How I checked it

  • scripts/dev/agent-preflight.sh
  • python3 scripts/dev/check-build-source-lists.py
  • bash 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.
  • Manual check, sleep: two real lid-closes on Justin's Mac, with both sides recorded after each wake.
  • Manual check, output change: during a meeting with a video playing, switch the output to AirPods and back. System audio should keep recording, and the log should show no "audio format changed" failure.

Risk Review

  • Privacy / local-first behavior reviewed: no data or logging change
  • Storage path or migration impact reviewed: none. The system WAV keeps the recording's first rate.
  • Public-facing copy stays concrete: no copy change
  • Release/update impact reviewed: no version, appcast or cask change
  • Agent PRs stay draft until human review
  • No private transcripts, audio, tokens, personal paths, or customer data are included

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

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
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
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
@claude claude Bot changed the title Keep meeting system audio alive across repeated sleep/wake Keep meeting system audio alive across repeated sleep/wake and output changes Sep 23, 2026
@claude
claude Bot marked this pull request as ready for review September 23, 2026 18:36
@claude
claude Bot merged commit b194069 into main Sep 23, 2026
7 checks passed
@claude
claude Bot deleted the claude/fix-system-audio-second-wake branch September 23, 2026 18:36
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
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.

2 participants