Conversation
…ia/Tahoe (#12) - Detect modern macOS (15+) where permission APIs are flaky - Suppress automatic re-prompts after initial grant/request - Treat SCStream error -3801 as transient on modern macOS - Persist prompt state in UserDefaults across app restarts - Properly stop polling when permission is granted - Allow user-initiated checks to always show dialog
PR Review: Fix Screen Recording Permission Re-prompting on macOS Sequoia/TahoeSummaryThis PR addresses issue #12 by implementing sophisticated logic to handle unreliable permission APIs on macOS 15+. The approach is well-reasoned and tackles a real macOS Sequoia/Tahoe bug with permission verification. ✅ Strengths1. Problem Understanding
2. Code Quality
3. Logic Improvements
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.
This PR is being reviewed by Cursor Bugbot
Details
You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
There was a problem hiding this comment.
Pull request overview
This PR fixes an issue where Screen Recording permission repeatedly prompts users on macOS Sequoia (15+) even after granting access. The root cause is that macOS 15+ has unreliable permission APIs that can return false negatives and transient errors. The solution implements intelligent suppression of automatic permission dialogs while preserving user-initiated permission checks.
Changes:
- Adds macOS version detection to identify macOS 15+ where permission APIs are unreliable
- Implements state persistence (verified permission, request history, last prompt date) across app restarts to prevent repeated automatic prompts
- Distinguishes between user-initiated and automatic permission checks, suppressing automatic re-prompts after initial interaction while allowing manual rechecks
- Treats error -3801 (SCStreamErrorUserDeclined) as transient on modern macOS rather than definitive denial
- Adds missing
stopPolling()calls in permission grant code paths
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| ScreenScribe/Sources/Services/ScreenCapturePermissionManager.swift | Adds persistence layer for permission state, implements modern macOS detection, adds userInitiated parameter to distinguish automatic vs manual checks, updates error handling and polling logic |
| ScreenScribe/Sources/Onboarding/PermissionStepView.swift | Marks the "Recheck Permission" button as user-initiated to bypass automatic suppression logic |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let timeSinceLastPopup = now.timeIntervalSince(lastTime) | ||
| shouldShowPopup = timeSinceLastPopup >= popupCooldownInterval |
There was a problem hiding this comment.
The cooldown logic compares the current time with persisted dates using timeIntervalSince. If the system clock is adjusted backward or the persisted date is somehow in the future, timeIntervalSince could return a negative value, causing timeSinceLastPopup >= popupCooldownInterval to be false even when it should be true. This could result in the permission dialog being suppressed indefinitely until the user manually triggers a recheck. Consider adding a check to handle negative time intervals or dates in the future by treating them as if the cooldown has expired.
| private var isModernMacOS: Bool { | ||
| ProcessInfo.processInfo.operatingSystemVersion.majorVersion >= 15 | ||
| } | ||
|
|
||
| private init() { |
There was a problem hiding this comment.
The isModernMacOS property uses majorVersion >= 15 to detect macOS Sequoia/Tahoe and newer. However, this is a computed property that's called multiple times throughout the code. Consider making this a stored property initialized once in init() for better performance, or add a comment explaining why it needs to be computed (in case the OS version could change at runtime, which is unlikely).
| private var isModernMacOS: Bool { | |
| ProcessInfo.processInfo.operatingSystemVersion.majorVersion >= 15 | |
| } | |
| private init() { | |
| private let isModernMacOS: Bool | |
| private init() { | |
| self.isModernMacOS = ProcessInfo.processInfo.operatingSystemVersion.majorVersion >= 15 |
| // On macOS Sequoia/Tahoe, these can also occur transiently during cold boot | ||
| let isDefinitiveDenial = error.code == -3801 | ||
| // On macOS Sequoia/Tahoe+, -3801 can still be transient, so only trust it on older macOS | ||
| let isDefinitiveDenial = error.code == -3801 && !isModernMacOS |
There was a problem hiding this comment.
On modern macOS (15+), error code -3801 is never treated as a definitive denial due to the condition !isModernMacOS. This means if a user explicitly revokes a previously granted permission in System Settings, verifyPermissionViaScreenCaptureKit returns .transientError instead of .denied, and the verifiedPermissionKey is never cleared. With hadPermissionBefore remaining true, the app will suppress automatic permission dialogs and poll silently indefinitely. The user would need to manually click a "Recheck Permission" button to trigger a new prompt. Consider adding logic to detect when permission has been revoked (e.g., checking for consistent -3801 errors over multiple polling iterations) and clearing the verified flag in that case, or documenting this as expected behavior.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8749329ad4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // On macOS Sequoia/Tahoe, these can also occur transiently during cold boot | ||
| let isDefinitiveDenial = error.code == -3801 | ||
| // On macOS Sequoia/Tahoe+, -3801 can still be transient, so only trust it on older macOS | ||
| let isDefinitiveDenial = error.code == -3801 && !isModernMacOS |
There was a problem hiding this comment.
Handle real -3801 denials on modern macOS
In verifyPermissionViaScreenCaptureKit, -3801 is no longer treated as a definitive denial on macOS 15+ (isDefinitiveDenial at line 78), but the downstream flow still routes prior-granted users through the transient path that only starts polling. When a user actually revokes Screen Recording permission after previously granting it, requestPermissionAndStartMonitoring (called automatically from App.proceedWithNormalStartup()) now keeps polling and never re-requests access, so the app can remain stuck in limited mode without an automatic recovery prompt.
Useful? React with 👍 / 👎.
Pull Request ReviewThis PR addresses a critical user experience issue on macOS 15+ (Sequoia/Tahoe) where screen recording permission dialogs repeatedly appear even after being granted. The implementation is well-thought-out and demonstrates deep understanding of the platform-specific permission system quirks. Code Quality & Best Practices ✅Strengths:
Minor Suggestions:
Potential Bugs & Issues
|
Closes #12
Problem
Screen Recording permission keeps re-prompting even after granting access on macOS Sequoia and Tahoe. The permission APIs on macOS 15+ can return false negatives and transient errors, causing the app to repeatedly show the system permission dialog.
Changes
isModernMacOSflag for macOS 15+ where permission APIs are unreliableuserInitiated: trueso explicit checks always work, but background checks don't spamSCStreamErrorUserDeclinedcan fire falsely on Sequoia/TahoehasRequestedPermissionandlastPromptDatein UserDefaults so state survives restartsstopPolling()in paths that were missing itNote
Medium Risk
Adjusts screen recording permission prompting and persistence logic; behavior changes could unintentionally suppress needed prompts or delay detection of granted permissions, especially on macOS 15+ where APIs are flaky.
Overview
Reduces repeated Screen Recording permission dialogs on macOS 15+ by distinguishing user-initiated vs automatic checks and suppressing automatic re-prompts after a prior prompt/verification, falling back to silent polling instead.
Updates
ScreenCapturePermissionManagerto persist prompt/verification state inUserDefaults(verified/requested flags and last prompt time), treat-3801as transient on modern macOS, and more consistently stop polling when permission is detected. The onboardingRecheck Permissionbutton now callsrequestPermissionAndStartMonitoring(userInitiated: true)to always allow an explicit re-prompt.Written by Cursor Bugbot for commit 8196500. This will update automatically on new commits. Configure here.