Document App/extension entitlements, bundle IDs, teams, and signing, flagging a code-vs-entitlement App Group ID mismatch (code uses group.app.timesafari.share while entitlements grant group.app.trentlarson.timesafari.share) that breaks shared storage.
9.6 KiB
iOS App Group Configuration Audit
Generated: 2026-06-25 17:31:15 PST
Scope
Static inspection of App Group configuration for the App target and the TimeSafariShareExtension target: entitlements, capabilities, bundle identifiers, Debug/Release build settings, and signing. No code was modified.
Files Inspected
| File | Role |
|---|---|
ios/App/App/App.entitlements |
App target App Group declaration |
ios/App/TimeSafariShareExtension/TimeSafariShareExtension.entitlements |
Extension App Group declaration |
ios/App/App.xcodeproj/project.pbxproj |
Bundle IDs, teams, signing, entitlement linkage |
ios/App/App/SharedImageUtility.swift |
App Group identifier used by main app code |
ios/App/TimeSafariShareExtension/ShareViewController.swift |
App Group identifier used by extension code |
CRITICAL FINDING — Code vs Entitlements App Group Mismatch
The entitlements and the Swift source declare different App Group identifiers:
| Location | App Group identifier |
|---|---|
App.entitlements |
group.app.trentlarson.timesafari.share |
TimeSafariShareExtension.entitlements |
group.app.trentlarson.timesafari.share |
SharedImageUtility.swift (appGroupIdentifier) |
group.app.timesafari.share |
ShareViewController.swift (appGroupIdentifier) |
group.app.timesafari.share |
The runtime code targets group.app.timesafari.share, but neither target is entitled to that group — both entitlements now grant group.app.trentlarson.timesafari.share.
This is an uncommitted change: git diff shows both entitlements were just changed from group.app.timesafari.share → group.app.trentlarson.timesafari.share, while the Swift code still uses the old value. Before this edit the code and entitlements matched; after it they do not.
Runtime Consequences
FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.app.timesafari.share")returns nil (the app is not entitled to that group). The extension'sstoreImageDataaborts viaguard let containerURL→ image file is never written; the main app's reads return nil.UserDefaults(suiteName: "group.app.timesafari.share")does not resolve to the shared, entitled suite. Writes fall back to each process's own preferences domain, so the extension's keys (sharedPhotoFilePath,sharedPhotoShareId,shareExtensionLastStart,sharedPhotoReady) are not visible to the main app.
Net effect: the entire share-target handoff via the App Group breaks while this mismatch exists. This is the most likely root cause of "App Group UserDefaults writes failing."
Note: This affects both Debug and Release (the entitlements have no per-configuration variants), not Debug only.
Direct Answers
Do both targets declare the same App Group?
Yes — the two entitlements files match each other. Both App.entitlements and TimeSafariShareExtension.entitlements declare exactly:
<key>com.apple.security.application-groups</key>
<array>
<string>group.app.trentlarson.timesafari.share</string>
</array>
However, the code does not match the entitlements (see Critical Finding). So "same App Group" is true at the entitlement level, false at the entitlement-vs-code level.
Are there any Debug vs Release differences?
Entitlements / App Group: No. A single entitlements file per target applies to both configurations; the App Group string is identical in Debug and Release (group.app.trentlarson.timesafari.share).
Bundle identifiers: Yes — they differ by configuration:
| Target | Debug | Release |
|---|---|---|
| App | app.trentlarson.timesafari |
app.timesafari |
| Extension | app.trentlarson.timesafari.TimeSafariShareExtension |
app.timesafari.TimeSafariShareExtension |
(The Debug bundle IDs were just changed from the app.timesafari* form per git diff.)
Development team: Yes — differs by configuration (see next answer).
In both configurations the extension bundle ID is correctly nested under the app bundle ID, which is required for an app extension.
Are there any team-ID differences that could affect App Group access?
| Configuration | App team | Extension team | Match? |
|---|---|---|---|
| Debug | 7XVXYPEQYJ |
7XVXYPEQYJ |
✅ same |
| Release | GM3FS5JQPH |
GM3FS5JQPH |
✅ same |
- Within each configuration, both targets use the same team — this is the condition required for two targets to share an App Group, and it is satisfied.
- Across configurations the teams differ (Debug
7XVXYPEQYJvs ReleaseGM3FS5JQPH). The Debug team was just changed fromGM3FS5JQPHpergit diff.
Implications:
- The App Group container is namespaced by Team ID at runtime (
$(TeamID).group...). A Debug install (team7XVXYPEQYJ) and a Release install (teamGM3FS5JQPH) use different physical containers and cannot share data with each other. This is normal and only matters if you expect data continuity between Debug and Release builds. - With Automatic signing, the App Group
group.app.trentlarson.timesafari.sharemust be registered/enabled for both teams. If it is not provisioned under the Debug team7XVXYPEQYJ, automatic signing of the Debug build can fail to include the App Group entitlement (or fail to sign), which would also break App Group access in Debug.
Are there signing/entitlement mismatches that could cause App Group UserDefaults writes to fail in Debug builds?
Yes. In order of severity:
-
(Primary) Code/entitlement group-ID mismatch. Code uses
group.app.timesafari.share; entitlements grantgroup.app.trentlarson.timesafari.share. The code's group is not entitled, so sharedUserDefaults/container access fails. Affects Debug and Release. -
(Debug-specific risk) App Group provisioning under the Debug team. Debug now signs with team
7XVXYPEQYJ(changed fromGM3FS5JQPH). Under Automatic signing, ifgroup.app.trentlarson.timesafari.shareis not enabled for team7XVXYPEQYJ, the Debug build's App Group entitlement may not be granted, causing writes to silently fall back to the local domain. -
(Consistency) Bundle-ID change accompanying the team change. Debug bundle IDs changed to
app.trentlarson.timesafari*. App Groups don't have to match bundle IDs, so this is not a direct cause, but combined with the new team it means Debug provisioning is a distinct profile/identifier set that must independently carry the App Group capability.
No mismatch was found between the two entitlement files themselves, and no per-configuration entitlement override exists.
Detailed Configuration
Entitlements (identical content in both files)
<key>com.apple.security.application-groups</key>
<array>
<string>group.app.trentlarson.timesafari.share</string>
</array>
CODE_SIGN_ENTITLEMENTS linkage (both Debug and Release):
| Target | Entitlements path |
|---|---|
| App | App/App.entitlements |
| Extension | TimeSafariShareExtension/TimeSafariShareExtension.entitlements |
Bundle Identifiers, Teams, Signing (project.pbxproj)
| Setting | App Debug | App Release | Ext Debug | Ext Release |
|---|---|---|---|---|
PRODUCT_BUNDLE_IDENTIFIER |
app.trentlarson.timesafari |
app.timesafari |
app.trentlarson.timesafari.TimeSafariShareExtension |
app.timesafari.TimeSafariShareExtension |
DEVELOPMENT_TEAM |
7XVXYPEQYJ |
GM3FS5JQPH |
7XVXYPEQYJ |
GM3FS5JQPH |
CODE_SIGN_STYLE |
Automatic | Automatic | Automatic | Automatic |
CODE_SIGN_ENTITLEMENTS |
App/App.entitlements |
same | TimeSafariShareExtension/...entitlements |
same |
| App Group (from entitlements) | group.app.trentlarson.timesafari.share |
same | same | same |
App Group Identifier Used in Code
// SharedImageUtility.swift:13 and ShareViewController.swift:13
private let appGroupIdentifier = "group.app.timesafari.share" // ← does NOT match entitlements
Recommendations (no code changed)
- Resolve the group-ID mismatch. Either revert the entitlements back to
group.app.timesafari.share, or update the two Swift constants togroup.app.trentlarson.timesafari.share. Both sides must use one identical string. - Confirm App Group provisioning per team. Ensure
group.app.trentlarson.timesafari.share(whichever string is chosen) is enabled for both7XVXYPEQYJ(Debug) andGM3FS5JQPH(Release) so Automatic signing includes the capability in both configurations. - Decide whether the Debug↔Release team/bundle-ID split is intentional. If cross-config data continuity is ever expected, note that different Team IDs yield different App Group containers.
- Verify at runtime using the existing
getShareExtensionDiagnostics()/[ShareTarget]logs: after aligning identifiers,shareExtensionLastStartwritten by the extension should become readable by the main app.
Conclusion
The two entitlement files agree on the App Group (group.app.trentlarson.timesafari.share) and, within each build configuration, both targets share the same Development Team and consistent nested bundle IDs — the structural requirements for App Group sharing are met. The decisive problem is that the Swift code still references the old group group.app.timesafari.share, which no entitlement grants; this breaks both shared UserDefaults and the shared container in all builds. Secondarily, the recent Debug switch to team 7XVXYPEQYJ means the chosen App Group must be provisioned under that team for Debug App Group access to work under Automatic signing.