Apple's rule is strict and simple: the entitlements in a signed binary must be a subset of the entitlements the provisioning profile grants. One extra key, or one key with a value the profile does not cover, and the install is rejected. The check happens at install time on the device and again during App Store Connect processing, which is why the same build can compile cleanly and still fail here.
The 0xE8008016 code is the device-side version. On upload you may instead see an ITMS rejection mentioning invalid entitlements or an unsupported capability, and Xcode sometimes reports it as a generic install failure. Same underlying mismatch.
Find the offending key
Do not guess at this. Two commands give you both sides of the comparison in about thirty seconds, and the extra key is usually obvious once they are side by side.
APP=build/MyApp.xcarchive/Products/Applications/MyApp.app # What the signature actually claims codesign -d --entitlements :- "$APP" > /tmp/app.plist # What the embedded profile grants security cms -D -i "$APP/embedded.mobileprovision" > /tmp/profile.plist /usr/libexec/PlistBuddy -x -c 'Print :Entitlements' /tmp/profile.plist > /tmp/granted.plist diff <(plutil -p /tmp/app.plist) <(plutil -p /tmp/granted.plist)
What the diff usually shows
- aps-environment
- Push notifications. The app says development or production and the profile grants the other one, or grants nothing because push was never enabled on the App ID. Archiving with a development profile is a frequent cause.
- com.apple.security.application-groups
- An App Group the App ID does not have, or one created in a different team. Extensions sharing a group with the host app need the group on both App IDs.
- com.apple.developer.associated-domains
- Universal links. Enabling the capability in Xcode adds the entitlement locally but does nothing to the App ID, so the profile stays behind.
- get-task-allow
- The debugging entitlement. It has to be true for development builds and false for distribution. A Release build signed with a Development profile carries it as true and gets rejected on upload.
- com.apple.developer.icloud-container-identifiers
- iCloud containers, which are separately configured on the App ID and are easy to have out of sync between Development and Production environments.
The fix
- 1
Enable the capability on the App ID
Apple Developer portal, Identifiers, your App ID, and tick the capability the diff pointed at. Some capabilities need extra configuration here, such as choosing the App Group or iCloud container.
- 2
Regenerate every profile for that App ID
Existing profiles were issued before the capability existed and do not gain it retroactively. Regenerate and download each one: Development, Ad Hoc, and App Store.
- 3
Replace the installed profiles and clean
Delete the old .mobileprovision files locally, install the new ones, then clean the build folder. The old profile is cached in derived data and will otherwise be embedded again.
- 4
Rebuild and re-run the diff
The two entitlement sets should now differ only by keys the profile grants and the app does not claim, which is allowed. Extra keys on the app side are what fail.
If the diff shows the app claiming something you do not actually use, the simpler fix is the other direction: remove the capability in Signing and Capabilities so the entitlement stops being written. Leftover entitlements from a feature that was abandoned six months ago are a real and annoying category of this bug.
Extensions have their own entitlements
Widgets, notification service extensions, and watch apps are signed separately, each with their own entitlements file and their own profile. The message may be about any of them, and the host app can be completely correct while an extension is not. Check each signed bundle.
for b in "$APP" "$APP"/PlugIns/*.appex "$APP"/Watch/*.app; do [ -e "$b" ] || continue echo "== $b" codesign -d --entitlements :- "$b" 2>/dev/null | plutil -p - done
The App Store Connect variant
On upload, the same mismatch is reported differently and often mentions a specific key. The two that come up most are get-task-allow being present on a Release build, and a beta-reports-active mismatch, which happens when a build signed with an Ad Hoc profile is uploaded for TestFlight. Both are really a case of the wrong profile type being used for the distribution method, rather than a capability problem.