Skip to content

Fix stale permission display for Screen Recording and Accessibility#17

Merged
execsumo merged 4 commits into
masterfrom
claude/fix-permissions-display-e5Rv9
May 21, 2026
Merged

Fix stale permission display for Screen Recording and Accessibility#17
execsumo merged 4 commits into
masterfrom
claude/fix-permissions-display-e5Rv9

Conversation

@execsumo

@execsumo execsumo commented May 20, 2026

Copy link
Copy Markdown
Owner

Summary

  • Bug fix: Screen Recording and Accessibility permissions now correctly show as granted after the user approves them in System Settings while the app is running
  • Root cause: CGPreflightScreenCaptureAccess() and AXIsProcessTrusted() cache their results per-process on macOS 15+; once they return false at launch, they never update
  • Fix: Added live non-cached checks — SCShareableContent.excludingDesktopWindows for screen capture, and AXUIElementCopyAttributeValue for accessibility — and show .granted if either the cached or live check succeeds
  • Tests: Extracted pure static helpers screenCapturePermissionState(syncGranted:liveGranted:) and accessibilityPermissionState(isTrusted:liveGranted:) on PermissionCenter, and added 9 tests in PermissionCenterTests.swift covering all boolean combinations including the regression cases
  • CI fixes: Added nonisolated to static helpers (Swift 6 @MainActor isolation), and removed non-existent AXError.notTrusted member (only AXError.apiDisabled exists in the enum)

Test plan

  • Build and run Heard.app via ./scripts/bundle.sh && open build/Heard.app
  • Grant Screen Recording in System Settings while app is running — status should flip to granted immediately
  • Grant Accessibility in System Settings while app is running — status should flip to granted immediately
  • swift run HeardTests — all 9 permission tests pass
  • CI green

claude added 4 commits May 20, 2026 15:19
CGPreflightScreenCaptureAccess() caches its result per-process on macOS 15+,
so granting Screen Recording in System Settings while the app runs never
flips the UI to "Granted". Fix by polling SCShareableContent.excludingDesktopWindows
every 3 s — this call goes through ScreenCaptureKit's own path and is not
subject to the same per-process cache.

AXIsProcessTrusted() can similarly return stale TCC data. Add a live fallback:
attempt AXUIElementCopyAttributeValue on the system-wide element; only
kAXErrorAPIDisabled and kAXErrorNotTrusted mean the process lacks permission,
all other results (including kAXErrorNoValue for "no focused app") confirm trust.

isScreenCaptureGranted and openScreenCaptureSettings are updated to use the
same live check so the rest of the app (tap preflight path, status banner) is
consistent with the Settings UI.
Extracts the OR-logic from screenCaptureState() and accessibilityState() into
public static helpers (screenCapturePermissionState / accessibilityPermissionState)
so the decision branches can be exercised without real OS API calls.

Tests cover all four boolean combinations for each permission, with explicit
regression cases for the macOS 15+ stale-TCC scenario: sync=false/live=true
(Screen Recording) and isTrusted=false/liveGranted=true (Accessibility), which
were the exact conditions that caused the "always shows not granted" bug.
Static methods on a @mainactor class inherit MainActor isolation in Swift 6,
making them uncallable from non-isolated contexts (compile error). The two
pure helper functions only operate on plain Bool/PermissionState values and
access no actor state, so nonisolated is correct — matching the existing
pattern used by MeetingDetector.isTeamsMainApp and cleanWindowTitle.
The AXError C enum does not define kAXErrorNotTrusted — the enum jumps
directly from kAXErrorAPIDisabled (-25211) to kAXErrorNoValue (-25212).
Removing the non-existent check; kAXErrorAPIDisabled alone correctly
identifies the "process not trusted" case.

https://claude.ai/code/session_01SLK18VAKHQdk37Mubvyp47
@execsumo
execsumo merged commit 8df0544 into master May 21, 2026
4 checks passed
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