ionic-team / ionic-team/capacitor
[Bug]: 9.0.0-alpha.6 `Cordova.xcframework` ships a nested `Capacitor.framework`, causing App Store upload validation to fail
- Dominant language
- TypeScript
- Stars
- 16.7k
- Forks
- 1.3k
- Avg merge
- 3d 15h
- Merged PRs (30d)
- 10
Description
### Capacitor Version
```
💊 Capacitor Doctor 💊
Latest Dependencies:
@capacitor/cli: 8.5.0
@capacitor/core: 8.5.0
@capacitor/android: 8.5.0
@capacitor/ios: 8.5.0
Installed Dependencies:
@capacitor/cli: 9.0.0-alpha.6
@capacitor/core: 9.0.0-alpha.6
@capacitor/android: 9.0.0-alpha.6
@capacitor/ios: 9.0.0-alpha.6
```
>
### Other API Details
```Shell
node --version output: v24.18.0
npm/pnpm: pnpm 11.20.0
pod --version output: n/a — SPM, not CocoaPods
Xcode: 26.2 (Build version 17C52)
Runner: GitHub Actions macOS 15.7.7, arm64
fastlane: 2.237.0 (gym + pilot)
capacitor-swift-pm: 9.0.0-alpha.6 (revision d72bb9e9e4b80e5c7f41ee3bf0d95008ab7dcfd8)
```
### Platforms Affected
- [x] iOS
- [ ] Android
- [ ] Web
### Current Behavior
I build my app with fast lane in GitHub Actions. After updating to alpha6, when I build in GHA, Fastlane fails with App Store Connect rejection:
```
[03:18:31]: --- Step: pilot ---
[03:18:31]: -------------------
[03:18:31]: Creating authorization token for App Store Connect API
[03:18:31]: Ready to upload new build to TestFlight (App: ***)...
[03:18:31]: Going to upload updated app to App Store Connect
[03:18:31]: This might take a few minutes. Please don't interrupt the script.
[03:18:57]: [altool] Running altool at path '/Applications/Xcode_26.2.app/Contents/SharedFrameworks/ContentDelivery.framework/Resources/altool'...
[03:18:57]: [altool]
[03:18:57]: [altool] 2026-08-08 03:18:57.835 ERROR: [ContentDelivery.Uploader.6000020D84C0]
[03:18:57]: [altool] =======================================
[03:18:57]: [altool] UPLOAD FAILED with 3 errors
[03:18:57]: [altool] =======================================
[03:18:57]: [altool] Failed to upload package.
[03:18:57]: [altool] 2026-08-08 03:18:57.836 ERROR: [altool.6000020D84C0] Validation failed (409) CFBundleIdentifier Collision. There is more than one bundle with the CFBundleIdentifier value 'com.capacitorjs.ios.Capacitor' under the iOS application 'App.app'. (ID: ffef7e96-1a5d-465b-8c8e-0c2bd9d922ba)
[03:18:57]: [altool] NSUnderlyingError : Validation failed (-19241) CFBundleIdentifier Collision. There is more than one bundle with the CFBundleIdentifier value 'com.capacitorjs.ios.Capacitor' under the iOS application 'App.app'.
[03:18:57]: [altool] status : 409
[03:18:57]: [altool] detail : CFBundleIdentifier Collision. There is more than one bundle with the CFBundleIdentifier value 'com.capacitorjs.ios.Capacitor' under the iOS application 'App.app'.
[03:18:57]: [altool] id : ffef7e96-1a5d-465b-8c8e-0c2bd9d922ba
[03:18:57]: [altool] code : STATE_ERROR.VALIDATION_ERROR
[03:18:57]: [altool] title : Validation failed
[03:18:57]: [altool] iris-code : STATE_ERROR.VALIDATION_ERROR
[03:18:57]: [altool] Validation failed (409) Invalid Bundle. The bundle at 'App.app/Frameworks/Cordova.framework' contains disallowed nested bundles. (ID: 8fbf2a00-cc2a-499f-8a69-8d43b2b08194)
[03:18:57]: [altool] NSUnderlyingError : Validation failed (-19241) Invalid Bundle. The bundle at 'App.app/Frameworks/Cordova.framework' contains disallowed nested bundles.
[03:18:57]: [altool] status : 409
[03:18:57]: [altool] detail : Invalid Bundle. The bundle at 'App.app/Frameworks/Cordova.framework' contains disallowed nested bundles.
[03:18:57]: [altool] id : 8fbf2a00-cc2a-499f-8a69-8d43b2b08194
[03:18:57]: [altool] code : STATE_ERROR.VALIDATION_ERROR
[03:18:57]: [altool] title : Validation failed
[03:18:57]: [altool] iris-code : STATE_ERROR.VALIDATION_ERROR
[03:18:57]: [altool] Validation failed (409) Invalid Bundle. The bundle at 'App.app/Frameworks/Cordova.framework' contains disallowed file 'Frameworks'. (ID: 475e5199-70c7-4ba5-b34d-4d621a909c66)
[03:18:57]: [altool] NSUnderlyingError : Validation failed (-19241) Invalid Bundle. The bundle at 'App.app/Frameworks/Cordova.framework' contains disallowed file 'Frameworks'.
[03:18:57]: [altool] status : 409
[03:18:57]: [altool] detail : Invalid Bundle. The bundle at 'App.app/Frameworks/Cordova.framework' contains disallowed file 'Frameworks'.
[03:18:57]: [altool] id : 475e5199-70c7-4ba5-b34d-4d621a909c66
[03:18:57]: [altool] code : STATE_ERROR.VALIDATION_ERROR
[03:18:57]: [altool] title : Validation failed
[03:18:57]: [altool] iris-code : STATE_ERROR.VALIDATION_ERROR
[03:18:57]: [altool]
[03:18:57]: Application Loader output above ^
[03:18:57]: [ContentDelivery.Uploader.6000020D84C0]
[03:18:57]: [altool.6000020D84C0] Validation failed (409) CFBundleIdentifier Collision. There is more than one bundle with the CFBundleIdentifier value 'com.capacitorjs.ios.Capacitor' under the iOS application 'App.app'. (ID: ffef7e96-1a5d-465b-8c8e-0c2bd9d922ba)
[03:18:57]: Could not download/upload from App Store Connect!
```
According to analysis by Opus:
The cause is the published `Cordova.xcframework` release artifact itself: it contains a
full second copy of `Capacitor.framework` nested inside `Cordova.framework/Frameworks/`.
Xcode copies the framework bundle verbatim into `App.app/Frameworks/`, so the nested copy
ships in the IPA.
My app does not use any Cordova plugins — `config.xml` is empty and the CLI's
`enableCordova` is false, so `CapApp-SPM/Package.swift` contains no Cordova
reference. `Cordova` is nevertheless pulled into the package graph because every official
plugin's `Package.swift` declares it unconditionally, e.g. `@capacitor/app`:
```swift
.target(
name: "AppPlugin",
dependencies: [
.product(name: "Capacitor", package: "capacitor-swift-pm"),
.product(name: "Cordova", package: "capacitor-swift-pm")
],
path: "ios/Sources/AppPlugin"),
```
All 17 plugins in my app do this. None of them `import Cordova`.
### Expected Behavior
iOS builds should complete without incident.
### Project Reproduction
no reproduction because the defect is in the capacitor-swift-pm release binary
### Additional Information
More from opus about how to confirm this:
No project is needed — the defect is in the published release binary and can be confirmed
in three commands:
```bash
curl -sLO https://github.com/ionic-team/capacitor-swift-pm/releases/download/9.0.0-alpha.6/Cordova.xcframework.zip
unzip -l Cordova.xcframework.zip | grep 'Cordova.framework/Frameworks/'
unzip -p Cordova.xcframework.zip \
'Cordova.xcframework/ios-arm64/Cordova.framework/Frameworks/Capacitor.framework/Info.plist' \
| plutil -p - | grep CFBundleIdentifier
```
Output:
```
0 2026-07-14 18:11 Cordova.xcframework/ios-arm64/Cordova.framework/Frameworks/
0 2026-07-14 18:11 Cordova.xcframework/ios-arm64/Cordova.framework/Frameworks/Capacitor.framework/
53467 2026-07-14 18:11 .../Frameworks/Capacitor.framework/native-bridge.js
819368 2026-07-14 18:11 .../Frameworks/Capacitor.framework/Capacitor
373 2026-07-14 18:11 .../Frameworks/Capacitor.framework/PrivacyInfo.xcprivacy
762 2026-07-14 18:11 .../Frameworks/Capacitor.framework/Info.plist
"CFBundleIdentifier" => "com.capacitorjs.ios.Capacitor"
```
The outer `Cordova.framework/Info.plist` is correct
(`com.getcapacitor.ios.CapacitorCordova`); the nested copy is what collides with the
top-level `Capacitor.framework`.
### Affected releases
Only 9.0.0-alpha.6. Verified by downloading each `Cordova.xcframework.zip`:
| Release | Published | `Cordova.framework/Frameworks/` entries | Zip size |
| --- | --- | --- | --- |
| 9.0.0-alpha.5 | 2026-06-23 | 0 | 1.84 MB |
| 8.4.2 | 2026-07-14 | 0 | 1.84 MB |
| **9.0.0-alpha.6** | **2026-07-14** | **present** | **4.98 MB** |
| 7.6.8 | 2026-07-16 | 0 | 1.50 MB |
| 8.5.0 | 2026-07-31 | 0 | 1.84 MB |
The ~3.1 MB size increase is the duplicated Capacitor binary.
### Root cause
Two changes combine, and only 9.x after 2026-07-09 has both:
1. **`capacitor-swift-pm` PR #37** (`a60b90c9`, 2026-07-09, "chore: update scheme based
optional cordova builds") gave Cordova its own `xcodebuild archive` invocation. Before
it, `Cordova.framework` was lifted out of the *Capacitor* scheme's archive and could
not carry an embedded copy. This is why alpha.5 is clean.
2. **`ios/CapacitorCordova/CapacitorCordova/Classes/Public/Plugin.swift`** exists only on
`main` (9.x) and begins with `import Capacitor`. It is absent on `8.x` — the contents
API returns 404 for that path on that branch. So on 9.x the Cordova target genuinely
depends on Capacitor and the archive embeds `Capacitor.framework` into
`Cordova.framework`. This is why 7.x and 8.x are clean even after PR #37.
`build-cap` in `capacitor-swift-pm` already strips exactly this kind of nesting, but only
for Capacitor — a workaround added in 2022 as `327984e5` ("fix: Add workaround for
embedded Cordova in Capacitor.framework"). PR #37 did not add the mirror-image lines for
Cordova:
```bash
rm -rf ./Build/Capacitor-iOS-Simulator.xcarchive/Products/Library/Frameworks/Capacitor.framework/Frameworks
rm -rf ./Build/Capacitor-iOS.xcarchive/Products/Library/Frameworks/Capacitor.framework/Frameworks
# ^^^^^^^^^ no equivalent pair for Cordova-iOS-Simulator / Cordova-iOS
```
### Proposed fix
In `ionic-team/capacitor-swift-pm`, `build-cap`, inside `create_xcframeworks()` after the
existing two `rm -rf` lines:
```bash
rm -rf ./Build/Cordova-iOS-Simulator.xcarchive/Products/Library/Frameworks/Cordova.framework/Frameworks
rm -rf ./Build/Cordova-iOS.xcarchive/Products/Library/Frameworks/Cordova.framework/Frameworks
```
The 9.0.0-alpha.6 artifacts need to be re-cut afterwards — `Package.swift` pins them by
SHA256 checksum, so no consumer picks up the fix until a new release is published.
Contributor guide
Assessment
This issue has not been assessed yet.