Recently, I worked on developing an Xcode source editor extension that needed to run some of our internal code formatters. These formatters are driven by configuration files that define how the tools should be executed. Because Xcode extensions must be sandboxed, they can’t directly access arbitrary file locations, including these configuration files.
To work around this, we used a container app to prompt users to select the location of the configuration files. We then created security-scoped bookmarks and passed them to the extension process. As expected, the standard way to share data between processes—such as an app and its extension—is by using Apple’s App Groups capability.
After setting this up, I noticed that the extension kept prompting the user to grant access to the shared files, even though both the app and extension were part of the same app group. This was unexpected—intuitively, accessing files within your own shared container shouldn’t trigger permission prompts.
The mistake
Coming from an iOS background, I defined the app group ID like this:
<key>com.apple.security.application-groups</key>
<array>
<string>group.example.app</string>
</array>
After running both the app and the extension and inspecting ~/Library/Group Containers/, it was clear that the shared container had been created. However, what I missed is that on macOS, App Group identifiers must be prefixed with the Team ID (for example, TEAMID.group.example.app). This allows the system to correctly associate the app group with your developer account and properly link the app and its extension.
Without this prefix, the container may still appear to exist, but entitlement validation and access behavior can be inconsistent—leading to issues like repeated permission prompts.
Conclusion
This turned out to be one of those frustrating issues where the root cause isn’t immediately obvious, even after checking open-source projects and documentation. To be fair, Apple does document this requirement—but it’s easy to overlook, especially since iOS does not require this detail and doesn’t expose the same behavior as clearly.