Macless
Blog $99, one-time →
← All posts
Troubleshooting

Why your TestFlight build doesn't show up after a successful upload

This is the most confusing failure mode in the whole pipeline, because your CI log shows green the entire time.

You push, the build runs, the archive and export steps succeed, the upload step succeeds, the workflow finishes green. You open App Store Connect to check on it and TestFlight shows... the previous build. Not a failed build. Not a processing build. Nothing new at all.

This one is genuinely confusing the first time it happens, because there's no error anywhere — everything you can see reports success.

What's actually happening

App Store Connect silently rejects an upload if it reuses the same marketing version and build number as a build you've already uploaded — even one from weeks ago. It considers that pair a duplicate. But the upload tool (xcrun altool, or xcrun notarytool/the newer transporter flows) reports a successful file transfer regardless — it uploaded the bytes fine, it just didn't tell you App Store Connect threw the result away on the other end.

So the failure genuinely happened, it's just invisible at the layer your CI log is watching.

The fix

Make sure your build number (CURRENT_PROJECT_VERSION in Xcode's build settings) changes on every single upload, even when your marketing version (1.2.0, the human-facing version) stays the same across several builds while you're iterating.

The reliable way to do this in GitHub Actions is to use GitHub's own per-workflow run counter instead of anything you maintain by hand:

xcodebuild archive \
  CURRENT_PROJECT_VERSION="$BUILD_NUMBER" \
  ...

# where, earlier in the workflow:
env:
  BUILD_NUMBER: ${{ github.run_number }}

github.run_number increments by one on every run of that specific workflow, forever, with no state you have to track yourself. It survives failed runs, manual re-runs, and branch changes — there's no scenario where two uploads accidentally land on the same number unless you've hardcoded it somewhere, which is the actual root cause most of the time: someone customized the archive step and dropped the dynamic build number in the process, usually while debugging something unrelated.

If you're already using a dynamic build number and this is still happening: double check the substitution is actually reaching the archive command — a common way this breaks is the environment variable being set in the wrong step, or in a shell context that doesn't inherit it, so $BUILD_NUMBER silently expands to empty and Xcode falls back to whatever's hardcoded in the project file.

How to confirm this is actually your problem

In App Store Connect, under your app's TestFlight tab, look at "Build Activity" or the processing history rather than just the current builds list — a duplicate-version upload sometimes doesn't even appear there, since it's rejected before entering the processing queue at all. If you genuinely see nothing, and your CI log shows a clean upload, mismatched version/build numbers is the first thing to check before anything else — it's the cause behind the overwhelming majority of "upload succeeded but nothing shows up" reports.

Worth separately ruling out: App Store Connect processing does take a few minutes even on a successful, non-duplicate upload — if it's only been a minute or two, it may simply not have finished processing yet.

This exact fix ships in the template

The Macless workflow file sets CURRENT_PROJECT_VERSION from github.run_number by default, so this failure mode doesn't happen unless you specifically override it. Every other silent failure I hit shipping Citolex is documented the same way.

See what's included — $99