Sandboxed app loses iCloud Drive access mid-session on macOS 26 — kernel refuses sandbox extension, FP client rejected (NSFileProviderErrorDomain -2001)

Starting somewhere around macOS 26.3, my sandboxed file manager spontaneously loses access to ~/Library/Mobile Documents mid-session.

Setup: at launch, the user grants access to '/', '/Users', or '~' via NSOpenPanel; I store a security-scoped bookmark and call startAccessingSecurityScopedResource(). This works fine - including iCloud Drive - until some point mid-session.

When it breaks, two things happen simultaneously:

  • Enumeration fails:

NSCocoaErrorDomain Code=257 (NSFileReadNoPermissionError)< NSPOSIXErrorDomain Code=1 (EPERM)

Console shows the kernel refusing extension issuance: couldn't issue sandbox extension com.apple.app-sandbox.read for '/Users/<user>/Library/Mobile Documents': Operation not permitted

  • And probing NSFileProviderManager confirms the process has been rejected system-wide:

NSFileProviderManager.getDomainsWithCompletionHandler > NSFileProviderErrorDomain Code=-2001 "The application cannot be used right now." (underlying Code=-2014)

What makes this specific to FP-backed paths: regular paths under the same '/' bookmark (~/Library/Application Support, etc.) stay accessible and recover normally with a fresh startAccessingSecurityScopedResource() call. Only ~/Library/Mobile Documents and its subtree fail - the entire tree, including the parent directory itself. Relaunch always restores access.

What I've tried and ruled out:

  • Re-resolving the bookmark + startAccessingSecurityScopedResource() - returns stale=false, granted=true but access is not restored; the kernel still refuses extension issuance for FP-traversing paths.
  • NSFileCoordinator coordinated read - doesn't help; the coordinator depends on the same sandbox extension the kernel is refusing.
  • Instantiating NSFileProviderManager(for: domain) per domain - fails with -2001 for every domain, confirming the rejection is process-wide, not path- or domain-specific.

My working theory: when a FileProvider daemon (bird/cloudd/fileproviderd) restarts mid-session, the process's FP-client XPC registration is invalidated, and the kernel subsequently refuses to issue sandbox extensions for any path served by FP - even with a valid bookmark. The process seems to have no API path to re-register its FP-client identity without relaunching.

Current workaround: I detect the -2001 response and prompt the user to relaunch, then do a programmatic self-relaunch if they confirm (which is obviously horribly intrusive).

Questions:

  1. Is there an API that lets a sandboxed consumer app reconnect its FP-client identity mid-session, short of relaunching?
  2. Is there an entitlement or capability that would make the kernel's extension issuance resilient to FP daemon restarts?
  3. Has anyone else hit this on 26.x and found a workaround?

Filed as FB22547671.

Starting somewhere around macOS 26.3, my sandboxed file manager spontaneously loses access to ~/Library/Mobile Documents mid-session.

Just to clarify, by "file manager" do you mean that your app is doing its own navigation of the broader file system or that your app is a file provider extension? If you're a file provider extension, why do you need/want access to so much of the system?

What makes this specific to FP-backed paths: regular paths under the same '/' bookmark (~/Library/Application Support, etc.) stay accessible and recover normally with a fresh start AccessingSecurityScopedResource() call. Only ~/Library/Mobile Documents and its subtree fail - the entire tree, including the parent directory itself.

What other directories have you tried? Particularly directories "user" directories like ~/Documents/ or ~/Downloads/? Looking at the bug, the current theory is that this is actually tied to a difference between the sandbox extension originally issued by the open panel and the new extension issued when the bookmark is resolved and accessed. Assuming that's correct then the issue here isn't FP itself, it's that FP paths are "user" directories while the other directories you check are not.

My working theory: when a FileProvider daemon (bird/cloudd/fileproviderd) restarts mid-session, the process's FP-client XPC registration is invalidated, and the kernel subsequently refuses to issue sandbox extensions for any path served by FP - even with a valid bookmark.

I think FP's involvement here is simply because it's dealing with a different class of

Relaunch always restores access.

When you relaunch are you presenting the open panel again or are you renewing an existing bookmark? I think you're presenting the open panel again on launch but if you're renewing a bookmark then I'll need to take a closer look at this.

Is there an entitlement or capability that would make the kernel's extension issuance resilient

I'm not sure which of these will work, but I can think of four different options/techniques that might improve/resolve the situation:

  1. When the error occurs, "reset" by presenting an open panel. That's obviously somewhat invasive and not something you'd necessarily want to ship; however, I would probably take the time to test it. If it works, then that confirms the issue is what I've described above, and it can be useful as an "escape hatch" option if none of the other options below work.

  2. When you're initially granted access, create bookmarks to the specific directories you're interested/concerned with, not just the original container. You're losing access when the original, broader, "grant" ends, but more specific bookmarks might work as long as they were created when your app had that broader access.

  3. It may be more access than you want your app to have, but I believe granting in FDA (Full Disk Access) would sidestep the issue.

  4. Similar to FDA, you can also use the "File Access Temporary Exceptions" to hardcode access to specific directories.

Finally, one other thing you could try tied to this:

...when the original, broader, "grant" ends...

My normal suggestion for apps that are going to use an open panel to preserve long-term access to a location is the following:

  1. Present the panel and have the user select the target.

  2. Take the original URL and convert it to a bookmark.

  3. Destroy the original URL.

  4. Resolve the bookmark whenever you need access to the target.

The idea here is to try and make your code more reliable/testable by having a single, consistent access source ("I get file access through my bookmark") instead of having a "split" path where your initial access came from the panel and future access came from the bookmark.

HOWEVER, there is another option if you're doing something like this at every launch:

at launch, the user grants access to '/', '/Users', or '~' via NSOpenPanel;

...which is to try and ensure ongoing access by preserving that original URL object and routing all activity through it. In an existing app like yours, that means taking the original URL and doing something like this:

  • Call "startAccessingSecurityScopedResource()" on it "a few times"[1].

  • Permanently store the URL "somewhere" and leave it alone.

The idea here is that by intentionally "leaking" the object, you reduce the risk of losing access by never releasing that access. That's obviously a bad idea for normal document access (eventually you'd run out of rights in the kernel), but doesn't matter if the object count is small and your app "always" needs access to them. Strictly speaking it isn't even really a "leak", as there's no reason to explicitly destroy a resource your app needs for its entire lifetime[2] when the kernel will clean everything up (through process destruction) much faster than you.

[1] In theory this isn't necessary; however, if you're going to leak then you might as well commit to it.

[2] As another example of this, most apps never actually "destroy" NSApplication and/or much of their broader UI infrastructure. Once NSApplication has cleared all of its termination check, it just calls "exit()", which then destroys "everything".

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Hello Kevin, and thank you for your answer!

Just to clarify, by "file manager" do you mean that your app is doing its own navigation of the broader file system or that your app is a file provider extension? If you're a file provider extension, why do you need/want access to so much of the system?

It is a general-purpose file manager (a "Finder replacement" for former Windows users), which is why it's essential for the app to have a user-defined, permanently-living bookmark. '/' is just an example - in practice users can (and often do) grant access to one or several specific subtrees.

When you relaunch are you presenting the open panel again or are you renewing an existing bookmark? I think you're presenting the open panel again on launch but if you're renewing a bookmark then I'll need to take a closer look at this.

I restore an existing bookmark from @AppStorage (UserDefaults-backed), and it always restores all access - including ~/Library/Mobile Documents - without presenting the panel again. The same bookmark re-resolution that works at launch fails for ~/Library/Mobile Documents mid-session, suggesting the issue is a change in process state rather than an inherent limitation of bookmark-resolved extensions on user directories.

What other directories have you tried? Particularly directories "user" directories like ~/Documents/ or ~/Downloads/? Looking at the bug, the current theory is that this is actually tied to a difference between the sandbox extension originally issued by the open panel and the new extension issued when the bookmark is resolved and accessed. Assuming that's correct then the issue here isn't FP itself, it's that FP paths are "user" directories while the other directories you check are not.

This is consistent with the user-directory-class difference you described - standard user directories like ~/Documents recover via bookmark re-resolution mid-session, while ~/Library/Mobile Documents does not. Combined with the relaunch observation above, the pattern seems to be: at process start, bookmark resolution succeeds for all paths; mid-session, after some daemon event, it stops working specifically for the harder user directories.

When the error occurs, "reset" by presenting an open panel. That's obviously somewhat invasive and not something you'd necessarily want to ship; however, I would probably take the time to test it. If it works, then that confirms the issue is what I've described above, and it can be useful as an "escape hatch" option if none of the other options below work.

When you're initially granted access, create bookmarks to the specific directories you're interested/concerned with, not just the original container. You're losing access when the original, broader, "grant" ends, but more specific bookmarks might work as long as they were created when your app had that broader access.

I'll test both suggestions at once by presenting an open panel targeted specifically at ~/Library/Mobile Documents when the failure is detected - if the approach below doesn't work out.

It may be more access than you want your app to have, but I believe granting in FDA (Full Disk Access) would sidestep the issue.

Similar to FDA, you can also use the "File Access Temporary Exceptions" to hardcode access to specific directories.

While this would probably be the easiest option, I'm afraid that App Review will likely push back on Temporary Exceptions for a general-purpose file manager.

Permanently store the URL "somewhere" and leave it alone.

This is the most interesting suggestion - I hadn't considered it. Currently the original URL from the panel is simply autoreleased by ARC once the initial setup is done. I'll keep the original URL with the intentional multiple startAccessingSecurityScopedResource() calls and test whether that keeps ~/Library/Mobile Documents alive across daemon restarts. I'll report back.

Thank you for the detailed analysis - the user-directory-class framing is a much cleaner explanation than what I had!

P.S. One more thought: is it possible the re-resolution simply happens too fast? If the failure is caused by a daemon restart, the daemon may not yet be ready to reissue sandbox extensions at the moment I retry. Relaunch always takes time, by which point the daemon has had time to recover. I'm going to also test a delayed retry (say, 10s) before concluding the state is unrecoverable.

It is a general-purpose file manager (a "Finder replacement" for former Windows users), which is why it's essential for the app to have a user-defined, permanently-living bookmark.

Got it. That's fine, just want to make sure I understand the general context.

I restore an existing bookmark from @AppStorage (UserDefaults-backed), and it always restores all access - including ~/Library/Mobile Documents - without presenting the panel again.

Huh. That's interesting, because the current theory on the bug side is that the bookmark isn't preserving a secondary attribute for user intent that the open panel includes. However, that wouldn't really make sense if you were always working out off a bookmark.

Similarly, clarification here:

This is consistent with the user-directory-class difference you described - standard user directories like ~/Documents recover via bookmark re-resolution mid-session, while ~/Library/Mobile Documents does not.

Making sure I'm understanding correctly, bookmarks targeting ~/Documents/ and ~/Downloads/ continue to work but ~/Library/Mobile Documents does not? Have you identified any other directories that fail at the same time? What about bookmarks to objects inside ~/Library/Mobile Documents? If this were purely about user intent issues, I'd expect "all" of them to fail at the same time.

One other test I'd throw into the mix - I would create a special bookmark (probably several) that specifically targets locations that are completely outside the user’s normal interaction locations but that you can't directly access (meaning, the user has to give you access to them). For example, some objects on an external volume.

Then, go into your normal reproduction process but making sure you DON'T interact with those "special" bookmarks/locations in any way. Finally, once the problem starts happening, see if you can resolve and access those "special". The point of this test is to specifically confirm that the low-level resolution process still "works" and that this isn't a broader/systemic failure. On the surface, you could do that by just resolving any bookmark, but the problem here is that objects that manage access in the kernel are tied to file system locations, not specific references. That means it's possible for:

  1. Your app to lose the ability to access new locations (typically because it's leaked/consumed to many security scope grants).

  2. Your app to still be able to access many locations (because the existing scope grants "happen" to cover those locations).

This can end up making a general breakdown look like a more specific failure, just because your existing set of grants (#2) happen to cover the "right" locations.

While this would probably be the easiest option, I'm afraid that App Review will likely push back on Temporary Exceptions for a general-purpose file manager.

Yes, absolutely. I think there may be value in testing both FDA and the access entitlements as a way to investigate what's going on, but they're definitely not what I would ship in a consumer product or expect the App Store to approve.

This is the most interesting suggestion - I hadn't considered it. Currently, the original URL from the panel is simply autoreleased by ARC once the initial setup is done. I'll keep the original URL with the intentional multiple startAccessingSecurityScopedResource() calls and test whether that keeps ~/Library/Mobile Documents alive across daemon restarts.

SO, the other variation on this approach, and what I would probably do if I were starting "from scratch", is to intentionally move ALL file handling/interaction out of your main "app" and into a helper tool. The app the user interacts with is (essentially) written as a "viewer" which then "displays" the file data your helper tool is accessing and retrieving. If/when "anything" goes wrong, your app just kills the helper tool, relaunches it, and then keeps going.

It's considerably more work [1], but the advantage of this approach is that it "hardens" your overall app experience against "whatever" happens in a way that the other approaches simply won't.

[1] I'm not sure how well this would work in practice, but in theory, you could move the VAST majority of your app’s entire implementation (all of its UI/views/etc.) into an app extension, which your app basically just presents one (or more) giant "view(s)". Your main app job is to present that view and maintain state information about where/what the user is doing (which the extension feeds back to it). If/when anything does wrong, your app simply terminates the existing extension and launches a new instance, using its state data to return back to its previous, exact, state [2].

[2] If your app state information is good enough, it's also possible for your main app to do exactly the same thing for "itself". When anything does wrong, your app captures its current state, uses NSWorkspace to kick off a new launch of itself, then terminates itself.

In both of the above cases, this would be somewhat user visible but, if done really well, minimally disruptive to the user.

P.S. One more thought: is it possible the re-resolution simply happens too fast?

It's certainly possible that timing is a factor here; however, it's not clear which daemon that would involve and why. Generally speaking, the resolution and security control process doesn't actually involve cloudd (it happens at a lower level of the system), so, theoretically, the cloudd itself shouldn't matter.

Having said that, it's certainly worth testing this:

I'm going to also test a delayed retry (say, 10s) before concluding the state is unrecoverable.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Sandboxed app loses iCloud Drive access mid-session on macOS 26 — kernel refuses sandbox extension, FP client rejected (NSFileProviderErrorDomain -2001)
 
 
Q