/ios-app-store-submit
Build, sign, and submit a Flutter/iOS app to the App Store Connect — covers Xcode archive/export, code signing (including headless-Mac keychain workarounds), the `asc` CLI for App Store Connect metadata automation, screenshot handoff, and final review submission. Use when asked
$ npx -y skills add zestfulpulse/ios-app-store-submit --skill ios-app-store-submit --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/ios-app-store-submit
Context preview
The summary Claude sees to decide when to auto-load this skill.
Build, sign, and submit a Flutter/iOS app to the App Store Connect — covers Xcode archive/export, code signing (including headless-Mac keychain workarounds), the `asc` CLI for App Store Connect metadata automation, screenshot handoff, and final review submission. Use when asked
SKILL.md
ios-app-store-submit.SKILL.mdname: ios-app-store-submit
description: Build, sign, and submit a Flutter/iOS app to the App Store Connect — covers Xcode archive/export, code signing (including headless-Mac keychain workarounds), the `asc` CLI for App Store Connect metadata automation, screenshot handoff, and final review submission. Use when asked to build and upload an iOS app, set up App Store code signing, fix a rejected/failed App Store Connect upload, automate App Store Connect metadata, or submit an app for App Store review. Triggers: "빌드해서 제출", "App Store 제출", "archive and upload", "TestFlight에 올려줘", "asc CLI로 메타데이터".
iOS App Store Build & Submit
End-to-end playbook for taking a Flutter/iOS app from "code is done" to "submitted for App Store review," written for headless/agent-driven Mac environments (no interactive Xcode GUI session, no GUI keychain prompts available). Validated end-to-end on a real submission (2026-08-05) that hit — and worked around — every pitfall documented below.
**This skill is project-agnostic.** Never hardcode bundle IDs, App Store Connect App IDs, Team IDs, or certificate names from a prior run — always derive them from the current project (see Step 0).
When NOT to use this
- Pure UI/feature work with no build/signing/submission component — just do the work directly.
- If the user has an interactive Xcode session open and wants to do signing themselves — offer to guide them through Xcode's GUI instead of fighting headless keychain limitations for no reason.
Step 0 — Discover the project
Before anything else, gather facts from the current project (don't ask the user for things you can read yourself):
# Bundle ID, team ID, current device family
grep -n "PRODUCT_BUNDLE_IDENTIFIER\|DEVELOPMENT_TEAM\|TARGETED_DEVICE_FAMILY" ios/Runner.xcodeproj/project.pbxproj | sort -u
# App version / build number
grep "^version:" pubspec.yaml # Flutter: X.Y.Z+N
# Is asc CLI already installed and authenticated?
which asc && asc auth status
# Is this app already registered in App Store Connect?
asc apps list --output table
If `asc` isn't installed or there's no cached auth, walk the user through generating an App Store Connect API key (they must do this themselves — only the account holder/Admin can): 1. https://appstoreconnect.apple.com/access/integrations/api → generate key → **App Manager** role (least privilege that still covers everything this skill needs) → download the `.p8` immediately (one-time download). 2. Get the file to you as a file (Drive link, etc.), never ask them to paste the raw key text in chat. Save it under `~/.asc/keys/` with `chmod 600`. 3. Register it:
asc auth login --name "<project>" --key-id "<KEY_ID>" --issuer-id "<ISSUER_ID>" \
--private-key "~/.asc/keys/AuthKey_<KEY_ID>.p8" --network --bypass-keychain**`--bypass-keychain` is required in headless sessions** — plain `asc auth login` fails with `-25308 User interaction is not allowed` because macOS Keychain wants a GUI unlock prompt that doesn't exist here.
Step 1 — iOS Simulator does not work for ML/vision-heavy apps on Apple Silicon
If the app uses `google_mlkit_*` (or similar plugins shipping arm64-simulator-incomplete binaries), **the app cannot even install on any iOS Simulator on an Apple Silicon Mac** ("Failed to find matching arch for input file"). Don't waste time debugging this — confirm real-device testing is the only option for that Mac, and check `flutter devices` for a wirelessly-paired iPhone/iPad before assuming none is available.
For screenshots specifically: if simulators are unusable, **don't fabricate screens**. Ask the user to capture the real screens on their device, or use `xcrun devicectl` to install/launch things yourself if the device is paired (see Step 6).
Step 2 — Code signing (the actual hard part)
2a. The core headless-Mac problem
The **login keychain requires interactive unlock** in most agent/headless sessions. Any operation that touches it — `asc auth login` without `--bypass-keychain`, `security import` into `login.keychain`, or even `codesign` using an *existing* identity that's already in `login.keychain` — can fail with `-25308 User interaction is not allowed` or `errSecInternalComponent`. This is true even for identities that work fine when the same Mac is used interactively (e.g., via a prior Xcode session).
**Fix: use a dedicated, non-interactive keychain for the whole session**, exactly like a CI runner would:
KC_PASS="build-$(date +%s | tail -c 6)"
security create-keychain -p "$KC_PASS" build.keychain
security unlock-keychain -p "$KC_PASS" build.keychain
security set-keychain-settings -lut 21600 build.keychain
security list-keychains -d user -s build.keychain login.keychain
Keep `$KC_PASS` around (write it to a gitignored file) — you'll need it again for `security set-key-partition-list`.
2b. Generate certificates without ever touching a keychain interactively
`asc certificates create --generate-csr` creates the private key and CSR as **plain files** — no keychain interaction at all — then submits the CSR to Apple:
asc certificates list --output table # check what already exists first
asc certificates create --certificate-type IOS_DISTRIBUTION --generate-csr \
--key-out ./signing/dist.key --csr-out ./signing/dist.csr \
--common-name "<App> Distribution" --email "<contact-email>"
# For a real-device dev/profile build later, also:
# asc certificates create --certificate-type IOS_DEVELOPMENT --generate-csr ...
Fetch/create the matching provisioning profile:
asc signing fetch --bundle-id "<bundle.id>" --profile-type IOS_APP_STORE \
--certificate-type IOS_DISTRIBUTION --create-missing --output ./signing
# For device installs: --profile-type IOS_APP_DEVELOPMENT --device "<ASC device ID>"
# (asc devices list to find/confirm the device is already registered)
2c. Import into build.keychain — OpenSSL 3.x needs `-legacy`
openssl x509 -inform
Read more
name: ios-app-store-submit description: Build, sign, and submit a Flutter/iOS app to the App Store Connect — covers Xcode archive/export, code signing (including headless-Mac keychain workarounds), the `asc` CLI for App Store Connect metadata automation, screenshot handoff, and final review submission. Use when asked to build and upload an iOS app, set up App Store code signing, fix a rejected/failed App Store Connect upload, automate App Store Connect metadata, or submit an app for App Store review. Triggers: "빌드해서 제출", "App Store 제출", "archive and upload", "TestFlight에 올려줘", "asc CLI로 메타데이터".
iOS App Store Build & Submit
End-to-end playbook for taking a Flutter/iOS app from "code is done" to "submitted for App Store review," written for headless/agent-driven Mac environments (no interactive Xcode GUI session, no GUI keychain prompts available). Validated end-to-end on a real submission (2026-08-05) that hit — and worked around — every pitfall documented below.
**This skill is project-agnostic.** Never hardcode bundle IDs, App Store Connect App IDs, Team IDs, or certificate names from a prior run — always derive them from the current project (see Step 0).
When NOT to use this
- Pure UI/feature work with no build/signing/submission component — just do the work directly.
- If the user has an interactive Xcode session open and wants to do signing themselves — offer to guide them through Xcode's GUI instead of fighting headless keychain limitations for no reason.
Step 0 — Discover the project
Before anything else, gather facts from the current project (don't ask the user for things you can read yourself):
# Bundle ID, team ID, current device family grep -n "PRODUCT_BUNDLE_IDENTIFIER\|DEVELOPMENT_TEAM\|TARGETED_DEVICE_FAMILY" ios/Runner.xcodeproj/project.pbxproj | sort -u # App version / build number grep "^version:" pubspec.yaml # Flutter: X.Y.Z+N # Is asc CLI already installed and authenticated? which asc && asc auth status # Is this app already registered in App Store Connect? asc apps list --output table
If `asc` isn't installed or there's no cached auth, walk the user through generating an App Store Connect API key (they must do this themselves — only the account holder/Admin can): 1. https://appstoreconnect.apple.com/access/integrations/api → generate key → **App Manager** role (least privilege that still covers everything this skill needs) → download the `.p8` immediately (one-time download). 2. Get the file to you as a file (Drive link, etc.), never ask them to paste the raw key text in chat. Save it under `~/.asc/keys/` with `chmod 600`. 3. Register it:
asc auth login --name "<project>" --key-id "<KEY_ID>" --issuer-id "<ISSUER_ID>" \
--private-key "~/.asc/keys/AuthKey_<KEY_ID>.p8" --network --bypass-keychain**`--bypass-keychain` is required in headless sessions** — plain `asc auth login` fails with `-25308 User interaction is not allowed` because macOS Keychain wants a GUI unlock prompt that doesn't exist here.
Step 1 — iOS Simulator does not work for ML/vision-heavy apps on Apple Silicon
If the app uses `google_mlkit_*` (or similar plugins shipping arm64-simulator-incomplete binaries), **the app cannot even install on any iOS Simulator on an Apple Silicon Mac** ("Failed to find matching arch for input file"). Don't waste time debugging this — confirm real-device testing is the only option for that Mac, and check `flutter devices` for a wirelessly-paired iPhone/iPad before assuming none is available.
For screenshots specifically: if simulators are unusable, **don't fabricate screens**. Ask the user to capture the real screens on their device, or use `xcrun devicectl` to install/launch things yourself if the device is paired (see Step 6).
Step 2 — Code signing (the actual hard part)
2a. The core headless-Mac problem
The **login keychain requires interactive unlock** in most agent/headless sessions. Any operation that touches it — `asc auth login` without `--bypass-keychain`, `security import` into `login.keychain`, or even `codesign` using an *existing* identity that's already in `login.keychain` — can fail with `-25308 User interaction is not allowed` or `errSecInternalComponent`. This is true even for identities that work fine when the same Mac is used interactively (e.g., via a prior Xcode session).
**Fix: use a dedicated, non-interactive keychain for the whole session**, exactly like a CI runner would:
KC_PASS="build-$(date +%s | tail -c 6)" security create-keychain -p "$KC_PASS" build.keychain security unlock-keychain -p "$KC_PASS" build.keychain security set-keychain-settings -lut 21600 build.keychain security list-keychains -d user -s build.keychain login.keychain
Keep `$KC_PASS` around (write it to a gitignored file) — you'll need it again for `security set-key-partition-list`.
2b. Generate certificates without ever touching a keychain interactively
`asc certificates create --generate-csr` creates the private key and CSR as **plain files** — no keychain interaction at all — then submits the CSR to Apple:
asc certificates list --output table # check what already exists first asc certificates create --certificate-type IOS_DISTRIBUTION --generate-csr \ --key-out ./signing/dist.key --csr-out ./signing/dist.csr \ --common-name "<App> Distribution" --email "<contact-email>" # For a real-device dev/profile build later, also: # asc certificates create --certificate-type IOS_DEVELOPMENT --generate-csr ...
Fetch/create the matching provisioning profile:
asc signing fetch --bundle-id "<bundle.id>" --profile-type IOS_APP_STORE \ --certificate-type IOS_DISTRIBUTION --create-missing --output ./signing # For device installs: --profile-type IOS_APP_DEVELOPMENT --device "<ASC device ID>" # (asc devices list to find/confirm the device is already registered)
2c. Import into build.keychain — OpenSSL 3.x needs `-legacy`
openssl x509 -inform
From automating App Store submission to deciding whether an iOS app is actually ready to submit, and helping recover when review fails.

