Ios auto release
Claude Code skills: auto-detect & start any app, README generator, Docker Hub push, LinkedIn posts, NotebookLM slides, secret scanner, mobile responsiveness testing | Install with npx skills add
npx -y skills add alfredang/skills --skill ios-auto-releaseAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Set up CI/CD that auto-builds, signs, uploads, and submits a native iOS/iPadOS app to App Store Connect on every push to main (GitHub Actions on macOS). Use when a user wants automated App Store releases / "auto submit a build on push" / a release pipeline for an Xcode app. Reuses the App Store Connect API key as the upload+submit token; handles signing assets as GitHub secrets, the iOS-26-SDK requirement, and the review-submission race.
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
11.6 KB, as published. Nobody here has run it
iOS auto-release CI/CD (GitHub Actions)
Org-standard signing credentials live in
org-credentials.envnext to this file (~/.claude/skills/ios-auto-release/org-credentials.env):ASC_KEY_ID,ASC_ISSUER_ID,ASC_PRIVATE_KEY_PATH,TEAM_ID=GU9WTSTX9M.install_on_device.shsources it as a fallback, so no need to hunt sibling.envfiles or ask the user. The.p8secret itself stays only at~/.appstoreconnect/private_keys/. A project's own.envoverrides these (setASC_BUNDLE_ID). To install a dev build on a connected device:bash ~/.claude/skills/ios-auto-release/scripts/install_on_device.sh.
Make every push to main build, sign, upload, and submit for review automatically.
The runner is macOS (Xcode is required to archive iOS apps). Auth for upload + submit is
the existing App Store Connect API key — no new token is needed (though a dedicated CI key
is good hygiene: Users and Access → Integrations → App Store Connect API → +).
The app record must already exist on App Store Connect (the API cannot create apps). Use the
app-store-submissionskill for the first manual submission; this skill automates the rest.
Fast path: upload a binary now via the ASC API (no CI round-trip)
To get a build into App Store Connect immediately from your Mac — instead of waiting on a
GitHub Actions run — use upload_binary.sh. It archives → exports an app-store-connect IPA →
uploads via xcrun altool --apiKey, reusing the org ASC API key as the upload token (no
Transporter/notarytool password). The build lands under the app's TestFlight / Builds tab a
few minutes later; attach it to a version and submit (UI, or ci_submit.py submit).
# app record + App ID + "Apple Distribution" cert + "<Scheme> App Store" profile must already exist
ASC_BUNDLE_ID=com.tertiaryinfotech.<app> \
bash ~/.claude/skills/ios-auto-release/scripts/upload_binary.sh
# Optional overrides: SCHEME, PROFILE, SIGN_IDENTITY, ENTITLEMENTS (e.g. a no-iCloud
# Local.entitlements), BUILD_NUMBER, SKIP_ARCHIVE=1 (re-export+upload an existing archive).
It auto-sources org-credentials.env + a project .env, signs manually with your
distribution cert + App Store profile, stamps a unique UTC build number, and warns about the
two most common altool rejections (missing AppIcon → 90022/90713) before the slow upload.
Use this for first releases and one-off pushes; use the GitHub Actions workflow below to make it
automatic on every push to main.
What it does each push
- Selects Xcode 26+ (App Store now rejects builds made with an older SDK).
- Imports the distribution certificate (
.p12) + App Store provisioning profile from secrets. - Computes the version + a unique build number, archives, exports (manual signing), uploads via
altool. - Waits for processing, then submits for review, reusing the in-review version or creating the next one
(carrying metadata + screenshots) — see
ci_submit.py.
Files to drop into the target repo
.github/workflows/ios-release.yml← fromtemplates/ios-release.yml(edit theenv:block)scripts/ci_submit.py← fromscripts/ci_submit.py(generic; no edits needed)ci/screenshots/APP_IPHONE_67/*.png← 1–3 real 6.9" screenshots, used when a new version is created
Edit the workflow's env: to your app: ASC_BUNDLE_ID, TEAM_ID, and (in the archive step)
the scheme, PROVISIONING_PROFILE_SPECIFIER, and CODE_SIGN_IDENTITY (usually Apple Distribution).
Gather the secrets (one-time)
REPO=<owner>/<repo>
KEYID=<ASC_KEY_ID>; ISSUER=<ASC_ISSUER_ID> # App Store Connect API key
P8=~/.appstoreconnect/private_keys/AuthKey_$KEYID.p8
# 1) ASC API key (upload + submit token)
gh secret set ASC_KEY_ID --repo $REPO --body "$KEYID"
gh secret set ASC_ISSUER_ID --repo $REPO --body "$ISSUER"
gh secret set ASC_API_PRIVATE_KEY --repo $REPO < "$P8"
# 2) Distribution certificate (cert + private key) as a password-protected .p12
# (exports the identities from your login keychain non-interactively)
security export -k ~/Library/Keychains/login.keychain-db -t identities \
-f pkcs12 -P "ci-temp-pass" -o /tmp/dist.p12
gh secret set DIST_CERT_PASSWORD --repo $REPO --body "ci-temp-pass"
base64 -i /tmp/dist.p12 | gh secret set DIST_CERT_P12_BASE64 --repo $REPO
# 3) App Store provisioning profile (download via ASC API or Xcode; install path below)
PROF="$HOME/Library/MobileDevice/Provisioning Profiles/<YourApp>_App_Store.mobileprovision"
base64 -i "$PROF" | gh secret set PROVISIONING_PROFILE_BASE64 --repo $REPO
Then gh secret list --repo $REPO should show all six. Trigger with a push or
gh workflow run ios-release.yml --repo $REPO.
Hard-won gotchas (already handled in the templates)
- iOS 26 SDK required. App Store Connect rejects uploads built with an older SDK
(409 "must be built with the iOS 26 SDK or later"). The workflow runs
maxim-lobanov/setup-xcodewithlatest-stable; ensure the runner image actually has Xcode 26 (macos-15/macos-26). - Export compliance must be answered or
submit409s. A build uploaded withoutITSAppUsesNonExemptEncryptioninInfo.plistlands withusesNonExemptEncryption = null(the encryption question is unanswered). Submitting that version fails with409 STATE_ERROR.ENTITY_STATE_INVALID— "appStoreVersions … is not in valid state … This resource cannot be reviewed" — with the real cause buried inassociatedErrorspointing at/v1/builds. Fix permanently: addITSAppUsesNonExemptEncryption=falsetoInfo.plist(XcodeGen: under the target'sinfo.properties) so every future build auto-answers. Fix a build already uploaded:PATCH /v1/builds/{id}{"attributes":{"usesNonExemptEncryption":false}}.ci_submit.pynow does this automatically inattach_build(setsfalsewhennull); usetrueonly if the app genuinely uses non-exempt encryption (then file the annual self-classification). - Externally-managed Python on macOS runners.
pip3 installfails with PEP 668; useactions/setup-python(the template does) andpip install. - Release archive defaults to automatic signing and fails for iCloud/other entitlements with the
team wildcard profile. The template forces
CODE_SIGN_STYLE=Manual+ an explicit profile. - Build number must be unique & increasing. The template uses a UTC timestamp
date -u +%y%m%d%H%M. - Marketing version must match the App Store version string for the build to attach —
ci_submit.pysets both (reuse the editable version, or bump the last component when the latest is released). - Review-submission race. Cancelling the current review is async and keeps "holding" the version,
so adding it to a new submission 409s.
ci_submit.pyretries, reusing an emptyREADY_FOR_REVIEWdraft instead of creating duplicates. - New versions need metadata + screenshots. When
ci_submit.pycreates the next version it copies the previous version's localization and uploadsci/screenshots/APP_IPHONE_67/*(commit real ones). - Release notes from
CHANGELOG.md.ci_submit.pyreads the section matching the version being submitted (falling back to[Unreleased]) and sets the version's What's New. It must run while the version is editable, so the script cancels the active review first, then sets it, then resubmits. This is a no-op on a first release (App Store allows "What's New" only on updates of an already-released app). - App Privacy + age rating are app-level and carry across versions — set once in the UI
(see the
app-store-submissionskill); CI does not touch them. - China mainland needs a publishing permit for any book/text content (Guideline 2.1). Apps that contain or display book, magazine, or scripture content — sutras, classics (道德经/金刚经), Tang poems, song lyrics, religious texts — are rejected in China mainland without a China NPPA Internet Publishing License (网络出版服务许可证). A Singapore/foreign entity (Tertiary Infotech) cannot obtain one, so deselect China mainland up front: Pricing and Availability → App Availability → Manage Availability → uncheck "China mainland" → Next → Confirm. The app then ships to every other territory and passes review; the choice is reversible if a permit is ever obtained. Make this the default for all Chinese-text / book-content apps to avoid a rejection round-trip. (This is an app-level availability setting, not a binary change — no rebuild needed; after removing China, just resubmit the rejected version with Update Review → Resubmit to App Review.)
Auto-install on your iPad on each release (local runner)
Cloud CI (GitHub Actions) cannot reach a physical iPad — it has no USB/network path to
your device. So on-device install of each release runs locally on your Mac (iPad connected,
Developer Mode on, device paired & trusted — see the ios-install-device skill).
Two pieces (in scripts/):
-
install_on_device.sh— builds a development copy of the current checkout and installs + launches it on all connected iPhones/iPads (matches byproductType, not the device's name). Regenerates the XcodeGen project and uses the.envASC key for-allowProvisioningUpdates. Restrict to specific devices by name withDEVICES, e.g.DEVICES="AA" bash …(iPad only). Run any time:bash .claude/skills/ios-auto-release/scripts/install_on_device.sh.This project's devices:
AA= iPad (iPad Air 11" M3),A2= iPhone (iPhone 16). By default a release installs on both; setDEVICES="AA,A2"(or a subset) to be explicit. -
watch_release_install.sh— pollsorigin/<branch>; when it advances (a new release in this CI's push-to-mainmodel) it checks that commit out in a throwawaygit worktreeand runs the installer. Your working tree is never touched (no pull/reset/checkout in place); the gitignored.envis copied into the worktree so signing works.
Run it as a LaunchAgent so each release auto-installs at login:
PLIST=~/Library/LaunchAgents/com.tertiaryinfotech.sketchbook.deviceinstall.plist
sed -e "s#__PROJECT_DIR__#$(pwd)#g" -e "s#__USER__#$USER#g" \
.claude/skills/ios-auto-release/templates/device-install.plist > "$PLIST"
launchctl load "$PLIST" # starts now + at every login; logs to /tmp/sketchbook-deviceinstall*.log
launchctl unload "$PLIST" # stop watching
Notes:
- It installs a development build (not the App Store binary — those are distribution-signed and can't sideload). It tracks the same source the release was cut from, so the device matches the release.
- Prefer tag-based releases? Set
BRANCHto a tag ref or adapt the watcher togit fetch --tags. - The Mac must be awake with the iPad reachable; if the device is offline a cycle is skipped and retried.
Caution
Auto-submitting every push to App Store review is aggressive (Apple dislikes churn). Prefer
gating on a tag or a manual workflow_dispatch if the user wants controlled releases — change the
on: trigger accordingly. The template submits on push to main (with doc/CI paths ignored).