# App Release

> End-to-end pipeline for releasing an iOS / watchOS app to TestFlight and the App Store. Use when the user wants to publish, ship, or release an iOS/watchOS app, get a build onto TestFlight, archive and upload via xcodebuild, deploy a CloudKit schema to Production, set App Privacy or export compliance, mint a Distribution certificate, or work through App Store Connect / Apple Developer portal steps. Triggers on 'continue publishing', 'ship the app to TestFlight', 'release the iOS app', 'upload a build', 'deploy CloudKit to production', 'App Privacy labels', 'distribution signing', or any cryptic Apple upload error (cloud signing, 90057 missing CFBundleShortVersionString, 90474 orientation). macOS and Xcode only.

- Skill: `glebis/app-release` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add glebis/app-release`
- Raw SKILL.md: https://api.skillmd.com/api/skills/glebis/app-release/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: glebis (https://skillmd.com/u/glebis)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/glebis/app-release

---


# App Release (iOS / watchOS → TestFlight / App Store)

Drive an iOS/watchOS app from "production path unstarted" to a Distribution-signed
build processing in TestFlight. The hard part is not any single step — it is that
the release path spans three disconnected web portals (Apple Developer, App Store
Connect, CloudKit Dashboard) plus CLI signing arcana, and **each gate hides a silent
failure discoverable only after the previous one passes.** Treat the whole thing as
an airlock checklist: each gate must verifiably seal before the next opens.

## When to use

Releasing/publishing/shipping an iOS or watchOS app; getting a build to TestFlight;
archiving + uploading; deploying CloudKit schema to Production; filling App Privacy
or export compliance; distribution signing; or debugging an Apple upload rejection.

## The gates (run in order; verify each before moving on)

Track every gate in the project's issue tracker. Do **not** assume a gate is closed
because a portal shows green — verify the underlying artifact.

1. **Inventory the gates.** List what is actually open (issue tracker, or derive
   from the steps below). Distinguish gates that block TestFlight from gates that
   block App Store submission — they are different.
2. **CloudKit → Production** (if the app uses CloudKit). Development indexes/record
   types do **not** exist in Production until an explicit *Deploy Schema Changes*.
   Deploy via the dashboard and verify the record type appears in the Production
   environment. **One-way action — confirm with the user first.**
3. **Export compliance.** Bake `ITSAppUsesNonExemptEncryption: false` into **every**
   shippable target (the app *and* the watch app), in the source of truth (e.g.
   XcodeGen `project.yml` info.properties), so TestFlight never prompts per build.
4. **App Privacy.** Audit the code for analytics / third-party SDKs / external
   networking first. If the developer cannot access user data (e.g. data lives only
   in the user's private CloudKit), the honest label is **Data Not Collected**. Set
   it in App Store Connect. Leave **Publish** for submission time.
5. **Privacy Policy + Impressum.** App Store requires a hosted privacy-policy URL;
   an EU/German operator also needs an Impressum. Add pages to the product site,
   deploy, paste the privacy URL into App Store Connect.
6. **Signing audit.** With automatic signing, Xcode mints the Distribution cert +
   App Store profiles at archive/export — no manual portal work. Confirm the team
   id and `CODE_SIGN_STYLE: Automatic`.
7. **Archive.** `xcodebuild archive` the iPhone scheme that *embeds* the Watch app,
   `-allowProvisioningUpdates`. (Archive signs with Apple Development; the
   Distribution cert is minted at the **export** step.)
8. **Export + upload to TestFlight.** `xcodebuild -exportArchive` with method
   `app-store-connect`, automatic signing, and an App Store Connect **API key**.
   The key must be **Admin** role — a Developer-role key cannot cloud-sign and fails
   with "Cloud signing permission error / no iOS Distribution cert found."
9. **Hand off the device test.** The final TestFlight verification (install the
   processed build, exercise the real feature loop on Production) is a **human**
   on-device step. Do not claim it as done.

**Read [references/pipeline.md](references/pipeline.md) for the exact commands,
ExportOptions.plist, and verification one-liners for every gate above.**

**When any step fails with a cryptic Apple error, read
[references/gotchas.md](references/gotchas.md) first** — it catalogs the silent
failures from real releases (corrupted SwiftPM checkout, Developer-role API key,
missing `CFBundleShortVersionString`, iPad-orientation 90474) with the exact fix.

## Guardrails (non-negotiable)

- **Confirm before one-way production actions**: CloudKit *Deploy to Production* and
  App Privacy *Publish*. They affect shipped users / the public product page.
- **Never print secret key values.** Keep `.p8` keys in `~/.appstoreconnect/`; clean
  up any temp copies after use.
- **Use `trash`, never `rm`.**
- **Drive logged-in portals via browser automation; never enter Apple credentials.**
  Ask the user to log in once in the automation browser.
- **Leave the on-device feature-loop test and the final Publish / Submit-for-review
  to the human.**
- **Commit only release-related changes** in focused commits; never sweep unrelated
  branch WIP into a release commit.

## Tooling notes

- Portal work (CloudKit Dashboard, App Store Connect, Apple Developer) is best driven
  through a persistent logged-in browser (e.g. the `browser-mate` skill) so the user
  logs in once and the session persists.
- The heavy build/sign/upload steps belong in the shell (`xcodebuild`), not GUI
  clicking — the CLI gives real error output instead of a progress bar. Reserve
  computer-use/GUI for steps that genuinely need it.
- Run long `xcodebuild` steps in the background and poll for `ARCHIVE SUCCEEDED` /
  `EXPORT SUCCEEDED` / `Upload succeeded` markers.

