← All posts Mobile

The Morning Every iOS Export Broke at Once

The archive succeeds and the export fails claiming your certificate is not in the provisioning profile. The certificate is fine. What is missing is the half of it Apple never had — and the fix is one flag on an import.

On the morning of August 19, 2026, every iOS export across five apps broke at once. Not a build failure — the archives succeeded. The export step failed, on all of them, with some variation of:

** ARCHIVE SUCCEEDED **
...
error: exportArchive No signing certificate "iOS Distribution" found
error: exportArchive Provisioning profile "iOS Team Store Provisioning Profile: com.example.app"
       doesn't include signing certificate "Apple Distribution: Example Inc"

The certificate was in Keychain Access. It was live on developer.apple.com. It had eight months left. The message names it explicitly and says it is not in the profile, so the obvious move is to go and look at the profile.

The profile was not the problem either.

An identity is two things, and one of them was gone

A signing identity is a certificate plus its private key. Apple only ever holds the certificate half. The key exists in exactly one place: your login keychain.

That key had vanished. The certificate it belonged to was still perfectly valid and completely useless, because the thing that proves you own it was gone.

Why it disappeared I never established, and this is worth knowing before you go looking: macOS keeps no keychain audit log. There is nothing to read. You get to find out that it happened and not why.

Once a private key is gone without a .p12 backup, it is not recoverable. The certificate has to be replaced.

One clue about how the setup got fragile: the cached profiles were named iOS Team Store Provisioning Profile: <bundle id>. That naming is Xcode's automatic-signing convention — which is also why no .p12 ever existed. Xcode created the identity implicitly, so nobody ever exported it, because nobody ever consciously made it.

Two dead ends worth skipping

Both of these are things I tried, and both cost real time. Naming them is most of the value here.

security set-key-partition-list

This is the standard advice for "keychain keeps prompting" and on this machine it could not work at all, in two distinct ways:

  • With -l "<certificate name>" it matches nothing — SecItemCopyMatching: item could not be found. The reason is subtle: -l filters on the key's label, and a key imported from a bare PEM does not inherit the certificate's name. You are searching for a label that was never set.
  • Without -l it walks every identity in the keychain and dies on the first one it cannot unlock — in my case a long-expired legacy key from years ago, with SecKeychainItemSetAccessWithPassword: passphrase not correct.

Both routes consume keychain password prompts and accomplish nothing. If you are being sent here, you are being sent somewhere that does not lead anywhere.

"An App Manager key cannot see distribution certificates"

Also commonly stated, also false. An App Manager App Store Connect API key lists API-created certificates fine.

The real rule appears to be different and more useful: the API does not show Xcode-managed certificates. So for anything Xcode created implicitly, the developer portal is ground truth; for anything created through the REST API, the API is authoritative. They are two partial views of the same list, and neither is complete.

The fix

Four steps, and the fourth is the one that actually matters.

1. Generate a key and a certificate signing request locally.

openssl genrsa -out dist.key 2048
openssl req -new -key dist.key -out dist.csr \
  -subj "/CN=Example Inc/O=Example Inc/C=US"

2. Create the certificate through the App Store Connect API. POST /v1/certificates with certificateType: DISTRIBUTION and the CSR, then base64-decode certificateContent into dist.cer.

Worth noting because it is counterintuitive: certificate creation via the REST API works even when Xcode's cloud signing does not. Cloud signing returns a permission error for an App Manager key; creating the certificate through the API is a different mechanism and is unaffected.

3. Create a provisioning profile. POST /v1/profiles with profileType: IOS_APP_STORE and relationships to the bundle ID and that certificate. Base64-decode profileContent into ~/Library/MobileDevice/Provisioning Profiles/<UUID>.mobileprovision, where the UUID comes from security cms -D on the profile. Only the top-level bundle ID needs one — embedded frameworks do not.

4. Import as PKCS#12, with -T. This is the whole trick.

openssl pkcs12 -export -inkey dist.key -in dist.cer \
  -name "Apple Distribution: Example Inc" -out dist.p12 -passout pass:"$P12PASS"

security import dist.p12 -k ~/Library/Keychains/login.keychain-db \
  -f pkcs12 -P "$P12PASS" \
  -T /usr/bin/codesign -T /usr/bin/security

A PKCS#12 import with -T sets a usable partition list at import time. Importing a bare PEM key does not. That single difference is why the same key can work or prompt forever depending on how it arrived.

No keychain password. No GUI dialog. And access stays scoped to codesign and security — do not reach for -A, which grants every application on the machine access to your signing key.

Then export with manual signing: signingStyle: manual, an explicit signingCertificate, and an explicit provisioningProfiles mapping.

Check in twenty seconds instead of ten minutes

A full export takes long enough that discovering the ACL is still blocked at the end of it is genuinely painful. Probe the key directly:

codesign --force --sign <CERT_SHA1> /tmp/probe.txt   # bound this to ~20s

Exit 0 means the export will get through. A hang means the keychain is still refusing.

Run it once. Do not poll it, and do not put it in a retry loop — every blocked attempt spawns SecurityAgent and puts a password dialog on the screen of whoever owns that Mac. A loop is a dialog every few seconds.

Two things that keep this from recurring

Export a .p12 backup of the live identity. The entire incident above exists because no backup existed. With one, the same failure is a two-minute import rather than a morning.

Share one distribution certificate across your apps. Apple caps Apple Distribution certificates at three per team. That is low, and if each app's setup creates its own you hit the ceiling quickly — at which point you are revoking certificates under time pressure, which is how the next outage starts. Note too that certificates sharing a common name cannot be told apart by name; check the SHA-1.

The fix is narrower than it looks

One last thing, because it is easy to over-correct here.

Archiving tolerates automatic signing. It signs with a development profile and that is fine. Only exportArchive needs an explicit distribution profile.

So the change is give the export step a profile, not convert the whole project to manual signing. A development-signed archive exported and uploaded cleanly the moment export had what it needed. Switching everything to manual would have meant touching five repositories to fix one step.

The short version

The error names the certificate, and the certificate is not the problem — the private key is. If you never exported a .p12, that key is gone and the certificate needs replacing. Import its replacement as a PKCS#12 with -T, skip set-key-partition-list entirely, probe with codesign before committing to an export, and back up the .p12 so the next time is five minutes instead of a morning.