I spent half an hour yesterday convinced something was broken on my end. It wasn't. Apple just doesn't tell you this rule anywhere obvious.
Here's what happened:
I'd fixed two real bugs in my app (Lockboxy, an encrypted vault for Mac/iOS) — one was a confusing error message, the other added zip preview support. Normal patch release. Same version number as my last TestFlight build (1.2.15), since nothing user-facing about the version had changed — just a new build number.
xcodebuild -exportArchive failed with:
error: exportArchive
code 90186: Invalid Pre-Release Train. The train version
'1.2.15' is closed for new build submissions.
error: exportArchive
code 90062: ... must contain a higher version than
that of the previously approved version.
Both errors, same root cause: 1.2.15 had already been approved and released live on the App Store. Once a version number ships, Apple closes its "pre-release train" — permanently. Doesn't matter that I only wanted it for TestFlight. Doesn't matter that no one outside my test group would ever see it. The version number itself is burned the moment it goes live.
The fix is simple once you know it: bump to a new version number (1.2.16), even for an internal TestFlight-only build. But here's the part that actually cost me time — the version number is baked into the binary at archive time, not export time. I bumped MARKETING_VERSION in the Xcode project, re-ran -exportArchive against my existing archive, and it exported fine with no error — but silently still reported the old version inside the binary. I had to throw away the archive and run xcodebuild archive again from scratch before export would actually reflect the new version.
So the real sequence, if you hit this:
- Bump your marketing version (and build number, while you're at it) in Xcode.
-
Re-archive from scratch. Don't reuse an old
.xcarchive—MARKETING_VERSIONis burned in at archive time. - Then export/upload as normal.
One side effect worth knowing if your app shows release notes in-app ("What's New"): if your code asserts the current app version matches the latest entry in your changelog data (mine does, as a test), bumping the version means you need a new changelog entry too — which, if you support multiple locales like I do (10 languages), means translating "What's New" into all of them before you can ship. Small cascade, easy to forget.
Nothing about this is documented clearly in one place — it's scattered across old Apple Developer Forums threads and Stack Overflow answers from 2019. If you're staring at code 90186 or 90062 right now: it's not your signing, not your provisioning profile, not your export options plist. It's just the version number. Bump it, re-archive, move on.
Top comments (0)