SuperAPI AI Directory

ios-build-sign

Deployment & operations

View repository

Updated 10-16 18:29

About this skill

Automate this: /mobile-release:ship-beta runs this skill end to end and uploads the result; /mobile-release:bootstrap sets up signing and CI from nothing. /mobile-release:doctor diagnoses a broken signing setup. What an agent may do unattended here: skills/release-automation.

Produce a signed, verified .ipa from an iOS source tree. Stop at a verified artifact on disk — this skill does not upload. Hand off to the app-store-publish skill for TestFlight and the App Store.

This skill requires macOS with Xcode installed. There is no Linux, Docker, or WSL path to a signed .ipa. If uname -s does not print Darwin, stop and say so — see Confirm the host.

Process:

  1. Confirm the host is macOS with a real Xcode, and pin the version — Confirm the host below.
  2. Discover the project — workspace vs project, scheme, configuration, resolved build settings, every embedded bundle — using references/discovery.md.
  3. Set the version and build number using references/versioning.md. CURRENT_PROJECT_VERSION must be strictly higher than every build already uploaded for this marketing version.
  4. Establish what is signing the build: certificates, profiles, entitlements, and automatic vs manual signing — references/signing-concepts.md.
  5. Set up the App Store Connect API key using references/asc-api-key.md. Copy the environment variable names character-for-character; a near-miss fails three steps later.
  6. On CI, create the keychain and import the identity using references/ci-keychain.md. Run that sequence exactly — it is what fastlane runs.
  7. If the team shares signing material across machines, use references/match.md. Read its §2 first: adopting match requires revoking your existing certificates.
  8. Archive and export with references/archive-export.md. Confirm the method value against xcodebuild -help on this machine — never a hardcoded list.
  9. Verify the .ipa with references/verification.md before handing it anywhere. Never report a build as done without quoting the Authority= line.
  10. To run all of this in CI, use the workflow in references/github-actions.md. On any failure, look the error up in references/troubleshooting.md before changing the build.

Load only the reference a step actually needs.

Contents

  • Confirm the host
  • Discover the project
  • Archive and export
  • Verify before upload
  • Troubleshooting
  • Core Instructions
  • Confidence and freshness
  • Output Format
  • References

Confirm the host

uname -s                 # must print: Darwin
sw_vers                  # macOS product and build version
xcode-select -p          # must be an Xcode.app path, not /Library/Developer/CommandLineTools
xcodebuild -version      # e.g. Xcode 16.4

xcode-select -p printing /Library/Developer/CommandLineTools means only the Command Line Tools are installed; the CLT cannot archive. Pin the Xcode version on CI with sudo xcode-select -s /Applications/Xcode_16.4.app — macos-latest rotates its default without notice, and a rotation changes both the SDK and the method values -exportArchive accepts.

If the host is Linux, say so and stop. The options are a macOS CI runner (references/github-actions.md), Xcode Cloud, or a Mac over SSH. Still doable from Linux: edit the project, write the ExportOptions.plist and the CI workflow, compute the next build number against the App Store Connect API, prepare secrets. None of it produces a binary.

Discover the project

ls -d *.xcworkspace *.xcodeproj 2>/dev/null    # a Podfile or .xcworkspace means use -workspace
xcodebuild -list -json -workspace MyApp.xcworkspace
xcodebuild -showBuildSettings -json \
  -workspace MyApp.xcworkspace -scheme MyApp -configuration Release \
  -destination 'generic/platform=iOS' > /tmp/buildsettings.json

Read out of those: PRODUCT_BUNDLE_IDENTIFIER, DEVELOPMENT_TEAM, CODE_SIGN_STYLE, CODE_SIGN_IDENTITY, PROVISIONING_PROFILE_SPECIFIER, CODE_SIGN_ENTITLEMENTS, MARKETING_VERSION, CURRENT_PROJECT_VERSION, DEBUG_INFORMATION_FORMAT. Two things fail a build before signing is reached: the scheme must be shared (in xcshareddata/xcschemes/, or CI sees no scheme at all) and its Archive action must use Release. Enumerate every embedded bundle — widgets, extensions, watch app, App Clip — before going further; under manual signing each needs its own profile and the same version and build number.

Archive and export

set -euo pipefail; set -o pipefail

NSUnbufferedIO=YES xcodebuild \
  -workspace MyApp.xcworkspace -scheme MyApp -configuration Release \
  -destination 'generic/platform=iOS' \
  -archivePath "$PWD/build/MyApp.xcarchive" \
  CURRENT_PROJECT_VERSION="$BUILD_NUMBER" \
  archive 2>&1 | tee build/archive.log | xcbeautify

NSUnbufferedIO=YES xcodebuild -exportArchive \
  -archivePath "$PWD/build/MyApp.xcarchive" \
  -exportPath  "$PWD/build/export" \
  -exportOptionsPlist "$PWD/ExportOptions.plist" \
  -allowProvisioningUpdates \
  -authenticationKeyPath "$PWD/private_keys/AuthKey_ABC1234567.p8" \
  -authenticationKeyID ABC1234567 \
  -authenticationKeyIssuerID 6053b7fe-68a8-4acb-89be-165aa6465141 \
  2>&1 | tee build/export.log | xcbeautify

-destination 'generic/platform=iOS' is what makes this a device archive rather than a simulator build. set -o pipefail is not optional — without it a failed build reports xcbeautify's exit status and looks green. The three -authenticationKey* flags must be passed together and are the only supported way to use -allowProvisioningUpdates with no signed-in Xcode account.

The method key is the most common export failure. Accepted values depend on the installed Xcode, and the old names (app-store, ad-hoc, development) are deprecated aliases of new ones (app-store-connect, release-testing, debugging). Read the list off the machine with xcodebuild -help | grep -A6 -i 'method' rather than trusting any document, this one included. The complete ExportOptions.plist, manual and automatic, plus the key reference: references/archive-export.md.

Verify before upload

unzip -q build/export/MyApp.ipa -d /tmp/v && APP="$(echo /tmp/v/Payload/*.app)"

codesign -dv --verbose=4 "$APP"                    # Authority, Identifier, TeamIdentifier
codesign --verify --deep --strict --verbose=2 "$APP"
codesign -d --entitlements :- "$APP"
security cms -D -i "$APP/embedded.mobileprovision" | plutil -p -
/usr/libexec/PlistBuddy -c 'Print :CFBundleVersion' "$APP/Info.plist"
ls build/MyApp.xcarchive/dSYMs/                    # dSYMs are NOT inside the .ipa

Pass criteria: Authority=Apple Distribution: … (not Apple Development), chain ending at Apple Root CA, no ProvisionedDevices in the profile, get-task-allow false, bundle ID identical in all three places, every embedded bundle on the same version and build, dSYM UUIDs matching the shipped binaries. Full command set and a one-shot script: references/verification.md.

Troubleshooting

Error Cause Fix
"No profiles for 'com.example.app' were found" No installed profile matches this bundle ID for this profile type — often the host app has one and an extension does not List installed profiles and compare their application-identifier against every bundle in the archive. Install the missing profile, or fastlane match appstore. To let Xcode create it, -allowProvisioningUpdates plus the -authenticationKey* trio
"Command CodeSign failed with a nonzero exit code" A wrapper message, never the cause Read the raw log a few lines above it, not the xcbeautify output — the real error is one of the rows below
errSecInternalComponent The keychain will not release the private key to codesign Run the ci-keychain.md §2 sequence. The missing line is almost always security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$PW" "$KEYCHAIN"
Build hangs at signing until the job times out Same cause, expressed as a blocking GUI prompt on a headless machine Identical fix. Never respond to this by raising the timeout
Keychain locked partway through the archive Default idle timeout is shorter than the build security set-keychain-settings -lut 21600 "$KEYCHAIN", and security unlock-keychain immediately before building
Wrong team ID in the signed binary Several teams visible to the account; DEVELOPMENT_TEAM empty or teamID absent from ExportOptions Set DEVELOPMENT_TEAM on the archive and teamID in ExportOptions.plist; confirm with the TeamIdentifier= line from codesign -dv --verbose=4
"…doesn't match the bundle identifier" Bundle ID differs between target, profile, and App Store Connect record Compare Info.plist CFBundleIdentifier, codesign -dv Identifier=, and the profile's application-identifier. They are case-sensitive, and the ASC record's ID cannot be changed
"Provisioning profile 'X' doesn't include the … entitlement" The .entitlements claims a capability the profile does not carry. Enabling it in the portal does not update existing profiles Enable the capability on the App ID, then regenerate the profile (match appstore --force) and reinstall. The binary's entitlements must be a subset of the profile's
"The bundle version must be higher than the previously uploaded version" CFBundleVersion was used before for this marketing version — including for a rejected or deleted build Raise the build number and rebuild. Build numbers are never reusable, and the number is inside the signed binary
"exportOptionsPlist error for key 'method'" The method value is not in this Xcode's vocabulary xcodebuild -help | grep -A6 -i method and use a value from that output

Full error strings, the five-command triage, and what to do when the error is not listed: references/troubleshooting.md.

Core Instructions

  • Never report a build as done without quoting the Authority= line from codesign -dv. Report the bundle ID, version, build number, signing identity and team ID every time.
  • Use manual signing for release builds. Automatic signing is permitted to create and rotate your team's certificates, and a team gets exactly one Apple Distribution certificate.
  • Keep destination as export in ExportOptions.plist, never upload, and manageAppVersionAndBuildNumber as false. Both make the shipped binary differ from the one you verified, and the upload leg belongs to app-store-publish.
  • Archive the dSYMs/ directory alongside the .ipa. It is not inside the .ipa, and without it this build's crash reports can never be symbolicated.
  • Never print a .p8, a .p12, MATCH_PASSWORD, or a keychain password into the transcript or a CI log. The .p8 is single-download: if it leaks, revoke it in App Store Connect immediately.
  • The repo .gitignore already blocks *.p8, AuthKey_*.p8, *.p12, *.cer and *.mobileprovision. That stops a commit, not a leak — signing material belongs in a secret store.

Confidence and freshness

Research date for this skill: 2026-08-05.

Confidence marks used throughout this skill and its references: [verified] — read off a primary vendor source on 2026-08-05. [snippet] — secondary source only, plausible but unconfirmed. [unverified] — not confirmed anywhere; run the check given before relying on it.

Solid: certificate types (Apple's own reference page); every security keychain command and flag (read out of fastlane's create_keychain.rb and keychain_importer.rb); every fastlane environment variable name (app_store_connect_api_key.rb, match/options.rb); API key creation and placement; setup_ci's behaviour.

Not solid: the ExportOptions.plist method value list is [snippet] only — Apple publishes no web copy of the xcodebuild man page and the mirror was proxy-blocked, so always run xcodebuild -help. Also unverified: provisioning profile types and their one-year expiry (Apple's page 404s), manageAppVersionAndBuildNumber, agvtool, match's subcommand list and the match AppStore <bundle_id> naming, and all codesign output shapes. Nothing here was executed — the authoring environment is Linux with no Xcode. Treat the first real run as the test.

Output Format

Report the artifact, the identity it was signed with, and the numbers someone needs before uploading. Never claim a build succeeded without a signing authority. State plainly what was not done.

Example output:

Release build — com.example.myapp

Artifact: build/export/MyApp.ipa — 42.1 MB Version: 3.8.0 · Build: 413 (last upload for 3.8.0 was 412) Toolchain: Xcode 16.4 (16F6) on macOS 15.5 · scheme MyApp, configuration Release

Signing — codesign -dv --verbose=4; codesign --verify --deep --strict exits 0:

Identifier=com.example.myapp
Authority=Apple Distribution: Example Ltd (ABCDE12345)
Authority=Apple Worldwide Developer Relations Certification Authority
Authority=Apple Root CA
TeamIdentifier=ABCDE12345

Profile: match AppStore com.example.myapp, expires 2027-06-14. No ProvisionedDevices, get-task-allow false, aps-environment = production. The two embedded bundles (…NotificationService, …WidgetExtension) each carry their own match AppStore profile, the same team, and the same 3.8.0 (413).

Kept alongside the artifact: build/MyApp.xcarchive/dSYMs/ (3 dSYMs, 61 MB) — UUIDs match the shipped binaries. DistributionSummary.plist and Packaging.log archived with the logs.

Not done: nothing was uploaded to App Store Connect and no TestFlight group was touched. The .p8, the temporary keychain and the installed profiles were deleted in the cleanup step. Run the app-store-publish skill against this .ipa for the upload.

End of example.

References

  • references/discovery.md — host and Xcode checks, what to do on Linux, workspace vs project, xcodebuild -list -json, -showBuildSettings, shared schemes, enumerating embedded bundles.
  • references/versioning.md — MARKETING_VERSION and CURRENT_PROJECT_VERSION, why build numbers must increase, agvtool, four ways to drive the build number from CI.
  • references/signing-concepts.md — certificate types, provisioning profile types, what codesign checks, entitlements in three places, automatic vs manual signing.
  • references/asc-api-key.md — creating the .p8, team vs individual keys, where the file must live, and the exact flag and environment variable names for xcodebuild, altool, notarytool and fastlane.
  • references/ci-keychain.md — the full security create-keychain → set-key-partition-list sequence line by line, installing profiles, fastlane setup_ci, producing the .p12, cleanup.
  • references/match.md — storage backends, the Matchfile, every option and env var, match nuke, when to use cert + sigh instead, a complete CI lane.
  • references/archive-export.md — the full archive and export invocations, the method trap, complete ExportOptions.plist for manual and automatic signing, key reference.
  • references/verification.md — codesign -dv, entitlement subset checks, decoding the embedded profile, per-bundle version checks, dSYM UUIDs, a one-shot verify script.
  • references/github-actions.md — complete macos-latest job: secrets, keychain, profiles, archive, export, verify, artifacts, cleanup.
  • references/troubleshooting.md — five-command triage plus the full error table.