Skip to content

Fix macOS 15+ permission caching causing stale Not Granted UI and generic transcript names#28

Merged
execsumo merged 3 commits into
mainfrom
claude/macos-screen-recording-perms-BzfpU
Jun 5, 2026
Merged

Fix macOS 15+ permission caching causing stale Not Granted UI and generic transcript names#28
execsumo merged 3 commits into
mainfrom
claude/macos-screen-recording-perms-BzfpU

Conversation

@execsumo

@execsumo execsumo commented Jun 5, 2026

Copy link
Copy Markdown
Owner

Two separate macOS 15+ TCC caching bugs:

  1. AXIsProcessTrusted() returns a stale false even when Accessibility is
    granted, causing extractMeetingTitle and RosterReader to short-circuit
    and return nil/[]. This makes every recording get an empty title, which
    produces generic date-only transcript filenames. Fix: remove the
    AXIsProcessTrusted() pre-checks; the underlying AXUIElementCopyAttributeValue
    calls already return .apiDisabled when access is genuinely denied.

  2. CGPreflightScreenCaptureAccess() and the CGWindowListCopyWindowInfo
    window-name probe both return stale false values on macOS 15+ for a
    fresh process whose screen recording was granted in a prior session.
    screenCaptureGrantedLive resets to false on every launch, so the UI
    shows "Not Granted" after a restart even though the grant is still in
    TCC. Fix: persist the confirmed-granted state to UserDefaults (same
    pattern already used for audioCaptureTCCGranted) and initialize from it
    at launch. Also update isAccessibilityGranted to use the same live AX
    API fallback already used by accessibilityState().

https://claude.ai/code/session_0141TjDnmekRsDDGoBu4jQyT

claude added 3 commits June 5, 2026 17:33
…eric transcript names

Two separate macOS 15+ TCC caching bugs:

1. AXIsProcessTrusted() returns a stale false even when Accessibility is
   granted, causing extractMeetingTitle and RosterReader to short-circuit
   and return nil/[]. This makes every recording get an empty title, which
   produces generic date-only transcript filenames. Fix: remove the
   AXIsProcessTrusted() pre-checks; the underlying AXUIElementCopyAttributeValue
   calls already return .apiDisabled when access is genuinely denied.

2. CGPreflightScreenCaptureAccess() and the CGWindowListCopyWindowInfo
   window-name probe both return stale false values on macOS 15+ for a
   fresh process whose screen recording was granted in a prior session.
   screenCaptureGrantedLive resets to false on every launch, so the UI
   shows "Not Granted" after a restart even though the grant is still in
   TCC. Fix: persist the confirmed-granted state to UserDefaults (same
   pattern already used for audioCaptureTCCGranted) and initialize from it
   at launch. Also update isAccessibilityGranted to use the same live AX
   API fallback already used by accessibilityState().

https://claude.ai/code/session_0141TjDnmekRsDDGoBu4jQyT
… bundle reset

TextInjector.inject(): AXIsProcessTrusted() stale-false caused all dictation
text injection to silently fail on macOS 15+. Added live AX API fallback
(same pattern as PermissionCenter.isAccessibilityGranted) — only returns
false when AXUIElementCopyAttributeValue returns .apiDisabled.

TextInjector.ensureAccessibility(): same stale-false could trigger a
redundant Accessibility permission prompt when the permission was already
granted. Added the live fallback before showing the dialog.

bundle.sh --reset: now also clears the screenCaptureTCCGranted and
audioCaptureTCCGranted UserDefaults keys. Without this, --reset wiped TCC
but left the cached-granted flags intact, causing the app to show
permissions as Granted immediately after a full reset.

setupAppAudioRecording: removed the CGPreflightScreenCaptureAccess() log
warning — it fires on every meeting start on macOS 15+ even with the
permission fully granted, producing false-positive noise that trains
developers to ignore real errors. The actual tap failure error at
AudioHardwareCreateProcessTap is the right signal.

https://claude.ai/code/session_0141TjDnmekRsDDGoBu4jQyT
All four CardRows were using isLast: index == perms.count - 1, which
draws a 0.5pt separator after every non-last row. Due to varying row
heights (rows with the Required badge and Grant button are taller than
those without), only the separator at the audioCapture→screenCapture
boundary landed on a crisp pixel, making it the only visible line.

Switch all permission CardRows to isLast: true so no separators are
drawn, matching the consistent no-divider appearance the user sees for
the other rows.

https://claude.ai/code/session_0141TjDnmekRsDDGoBu4jQyT
@execsumo execsumo merged commit b4e9265 into main Jun 5, 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