fix(calendar): singleton EKEventStore + macOS 26 stale status fix#3948
Merged
louis030195 merged 3 commits intoJun 10, 2026
Merged
Conversation
…ion prompts - Replace multiple EKEventStore instances with singleton pattern - Each ScreenpipeCalendar::new() created 2 EKEventStore instances (30+ total) - This caused multiple permission prompts and EventKit blocking - Now all calls share ONE EventsManager/EKEventStore via OnceLock - Thread-safe with Send + Sync traits - Reduces EKEventStore instances from 30+ to 1 - Prevents EventKit blocking and permission spam - All 15+ calls across the app now reuse the same instance
macOS 26's static authorizationStatusForEntityType: can keep returning a non-FullAccess value for minutes after an in-process grant. request_access() returned granted, but every subsequent read rebuilt a fresh EKEventStore, re-checked the stale static status, and hard-failed with AuthorizationDenied. A freshly connected calendar therefore returned HTTP 500 on /connections/calendar/events and listed nothing, so users re-clicked Connect in a loop until the OS cache eventually refreshed (or they relaunched). - Remember a successful grant in-process (ACCESS_GRANTED_THIS_SESSION). - Read gate: FullAccess reads; Denied/Restricted block; anything else with a grant seen this session calls reset() and reads anyway instead of failing. - Bump macOS calendar read logging debug -> info/warn and log the live auth status on failure (Windows already did this) so the next case is visible. - Combined with singleton pattern: ONE EKEventStore instance + trust in-process grants = reliable calendar permissions on macOS 26+.
Contributor
Author
Screen.Recording.2026-06-10.at.4.10.14.AM.mov |
Contributor
Author
|
@louis030195 This PR supersedes #3936 |
louis030195
reviewed
Jun 10, 2026
louis030195
left a comment
Collaborator
There was a problem hiding this comment.
nice. @divanshu-go two small things:
generated by the screenpipe pr-review pipe (https://screenpi.pe), not written by a human — reply and tag @louis030195 if it got something wrong.
| let status = ScreenpipeCalendar::authorization_status(); | ||
| if format!("{}", status) != "Full Access" { | ||
| if format!("{}", status) != "Full Access" | ||
| && !ScreenpipeCalendar::access_granted_this_session() |
Collaborator
There was a problem hiding this comment.
why format status to string? can we use an is_authorized() helper?
| }) | ||
| .0 | ||
| } | ||
|
|
Collaborator
There was a problem hiding this comment.
is Sync safe without a mutex? ekeventstore shouldn't be used concurrently
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Two combined fixes for Apple Calendar permissions on macOS:
EKEventStoreinstancesRoot Cause
Issue 1: Multiple EKEventStore instances
Every
ScreenpipeCalendar::new()created twoEKEventStoreinstances (one insideEventsManager, one explicit). With 15+ calls across the app, that's 30+ instances — triggering Apple's EventKit anti-spam protection, causing multiple permission prompts and eventually blocking the app entirely.Issue 2: macOS 26 stale authorization status (supersedes #3936)
authorizationStatusForEntityType:keeps returning a non-FullAccess value for minutes after an in-process grant.request_access()returnedgranted = true, but every read rebuilt a freshEKEventStore, re-checked the stale static status, and hard-failed withAuthorizationDenied. Result:GET /connections/calendar/eventsreturned HTTP 500 in a loop until the OS cache refreshed or the user relaunched.Fix
Singleton
OnceLock<SingletonManager>ensures exactly ONEEventsManager/EKEventStoreis ever createdget_singleton()Send + Synctraits on the wrapperTrust in-process grants
ACCESS_GRANTED_THIS_SESSIONflag remembered after a successfulrequest_access()FullAccess→ read;Denied/Restricted→ block; anything else with a session grant → callreset()and read anywaydebug!toinfo!/warn!so the next occurrence is visible in logsImpact
References