Six findings from diagnosing a real report of "Google Meet in Chrome is detected but never records". They share one root: the consent prompt is a user notification, so the whole feature depends on that notification being SEEN, and nothing in the app or the tests checks that.
What works and is not in question: detection itself. Measured on the CI runner, a live Meet page makes Chrome hold NoIdleSleepAssertion named "WebRTC has active PeerConnections", which the detector matches, and the full chain (detect → prompt → consent → record → pipeline job) completes once the prompt is actually visible.
1. The prompt is invisible under Do Not Disturb, and the app cannot tell
makeNotificationContent (NotificationManager.swift) sets title, body, sound and category, but no interruptionLevel. The default is .active, which macOS renders as a banner and suppresses entirely under any Focus mode.
The prompt expires after consentPromptTimeout = 60 and an expiry counts as a decline, followed by a cooldown and a re-prompt. So for a user with Do Not Disturb on, browser-meeting recording is silently and permanently dead: it detects, it asks, the question is never seen, forever.
Reproduced on a real machine: authorization authorized, Do Not Disturb active, prompts land silently in Notification Center and expire unanswered.
interruptionLevel = .timeSensitive is the candidate: it breaks through Focus and is exactly the case Apple designed it for. Worth pairing with a review of whether 60 s is the right window at all.
2. The delivered flag does not mean delivered
notify(...) and askToRecord(...) record to the ring buffer with delivered = isSetUp && canDeliver(), and canDeliver only checks Bundle.main.bundleIdentifier != nil. It reflects the app's intent to post, never macOS's decision to show.
Measured on the reported machine: 31 of 31 entries flagged delivered: true while the user saw none of them.
This matters beyond diagnostics, because scripts/e2e-browser.sh now asserts exactly this flag as proof that "the app actually posted one". That assertion is weaker than its own comment claims. The lane is still protected by its pre-flight authorization check, but the flag itself should either be renamed to what it means or backed by getDeliveredNotifications.
3. A parked prompt blocks the watch loop for up to 60 s
shouldDeferForConsent is awaited inline in the single sequential poll loop (WatchLoop.swift), so while a prompt is parked detector.checkOnce() is not called at all. A native Teams or Zoom meeting starting inside that window is not handled until the prompt resolves or times out.
Unit tests use an instantly-resolving consent spy and the e2e lane grants within ~2 s, so nothing exercises the parked case. Whether to move consent off the loop is a design decision; at minimum the current behaviour deserves a test that pins it.
4. Google Meet raises the assertion on pages you cannot join
Measured on the runner: opening a Meet link anonymously lands on "You can't join this video call", and Chrome held "WebRTC has active PeerConnections" on that page for the full 100 s of the probe.
So an expired link, a wrong code, an uninvited join or an already-ended meeting all trigger the consent prompt with no meeting anywhere. On its own that costs one prompt, which is what the consent gate is for. Combined with finding 3 it means a failed Meet join freezes native meeting detection for a minute.
The local WebRTC fixture cannot reproduce this, since it only ever represents a working call.
5. The Record action uses .foreground
makeConsentCategory() declares UNNotificationAction(identifier: recordActionID, title: "Record", options: [.foreground]). The delegate callback fires either way, so the flag only causes the app to be activated, taking focus away from the meeting the user just consented to keep having.
Secondary effect on developer machines: with several bundles registered for the same bundle id, LaunchServices activates whichever it considers canonical, which can be a different copy than the running one, so clicking Record starts a second instance. Measured: 17 registered paths for the dev bundle id, and Record launched the ~/Applications copy while a worktree build was the one running. Not a user-facing problem (one installed bundle), but it points at the flag being unnecessary.
6. The Settings warning covers authorization only
The warning added recently (BrowserConsentReadiness) decides on UNAuthorizationStatus. Authorized-but-not-visible is a third state it does not model: with Do Not Disturb active, or the alert style set to none, the status is .authorized and the warning stays silent while the feature is just as dead.
The reported user was in exactly that state, so the warning would have told them nothing.
Suggested order
1 and 6 are the user-facing fix and belong together. 2 hardens the lane assertion that is currently overstated. 3 and 4 are one scenario seen from two ends and are worth deciding on together. 5 is a small independent cleanup.
Six findings from diagnosing a real report of "Google Meet in Chrome is detected but never records". They share one root: the consent prompt is a user notification, so the whole feature depends on that notification being SEEN, and nothing in the app or the tests checks that.
What works and is not in question: detection itself. Measured on the CI runner, a live Meet page makes Chrome hold
NoIdleSleepAssertion named "WebRTC has active PeerConnections", which the detector matches, and the full chain (detect → prompt → consent → record → pipeline job) completes once the prompt is actually visible.1. The prompt is invisible under Do Not Disturb, and the app cannot tell
makeNotificationContent(NotificationManager.swift) sets title, body, sound and category, but nointerruptionLevel. The default is.active, which macOS renders as a banner and suppresses entirely under any Focus mode.The prompt expires after
consentPromptTimeout = 60and an expiry counts as a decline, followed by a cooldown and a re-prompt. So for a user with Do Not Disturb on, browser-meeting recording is silently and permanently dead: it detects, it asks, the question is never seen, forever.Reproduced on a real machine: authorization
authorized, Do Not Disturb active, prompts land silently in Notification Center and expire unanswered.interruptionLevel = .timeSensitiveis the candidate: it breaks through Focus and is exactly the case Apple designed it for. Worth pairing with a review of whether 60 s is the right window at all.2. The
deliveredflag does not mean deliverednotify(...)andaskToRecord(...)record to the ring buffer withdelivered = isSetUp && canDeliver(), andcanDeliveronly checksBundle.main.bundleIdentifier != nil. It reflects the app's intent to post, never macOS's decision to show.Measured on the reported machine: 31 of 31 entries flagged
delivered: truewhile the user saw none of them.This matters beyond diagnostics, because
scripts/e2e-browser.shnow asserts exactly this flag as proof that "the app actually posted one". That assertion is weaker than its own comment claims. The lane is still protected by its pre-flight authorization check, but the flag itself should either be renamed to what it means or backed bygetDeliveredNotifications.3. A parked prompt blocks the watch loop for up to 60 s
shouldDeferForConsentis awaited inline in the single sequential poll loop (WatchLoop.swift), so while a prompt is parkeddetector.checkOnce()is not called at all. A native Teams or Zoom meeting starting inside that window is not handled until the prompt resolves or times out.Unit tests use an instantly-resolving consent spy and the e2e lane grants within ~2 s, so nothing exercises the parked case. Whether to move consent off the loop is a design decision; at minimum the current behaviour deserves a test that pins it.
4. Google Meet raises the assertion on pages you cannot join
Measured on the runner: opening a Meet link anonymously lands on "You can't join this video call", and Chrome held
"WebRTC has active PeerConnections"on that page for the full 100 s of the probe.So an expired link, a wrong code, an uninvited join or an already-ended meeting all trigger the consent prompt with no meeting anywhere. On its own that costs one prompt, which is what the consent gate is for. Combined with finding 3 it means a failed Meet join freezes native meeting detection for a minute.
The local WebRTC fixture cannot reproduce this, since it only ever represents a working call.
5. The
Recordaction uses.foregroundmakeConsentCategory()declaresUNNotificationAction(identifier: recordActionID, title: "Record", options: [.foreground]). The delegate callback fires either way, so the flag only causes the app to be activated, taking focus away from the meeting the user just consented to keep having.Secondary effect on developer machines: with several bundles registered for the same bundle id, LaunchServices activates whichever it considers canonical, which can be a different copy than the running one, so clicking Record starts a second instance. Measured: 17 registered paths for the dev bundle id, and Record launched the
~/Applicationscopy while a worktree build was the one running. Not a user-facing problem (one installed bundle), but it points at the flag being unnecessary.6. The Settings warning covers authorization only
The warning added recently (
BrowserConsentReadiness) decides onUNAuthorizationStatus. Authorized-but-not-visible is a third state it does not model: with Do Not Disturb active, or the alert style set to none, the status is.authorizedand the warning stays silent while the feature is just as dead.The reported user was in exactly that state, so the warning would have told them nothing.
Suggested order
1 and 6 are the user-facing fix and belong together. 2 hardens the lane assertion that is currently overstated. 3 and 4 are one scenario seen from two ends and are worth deciding on together. 5 is a small independent cleanup.