| name | ship-ios-app |
| description | Ships new versions of iOS/macOS apps. Use when asked to ship, release, or deploy an app, or when checking if a version is ready to ship. |
| allowed-tools | Read, Bash, Grep, Glob, Edit, Task |
Ship iOS/macOS App Version
This skill manages the release workflow for iOS and macOS apps distributed via App Store Connect.
Pre-flight Checks
Before shipping, verify ALL of the following conditions:
1. Check for Ready Pull Request
gh pr list --state open --json number,title,headRefName,statusCheckRollup
A PR is ready to ship ONLY if:
- There is an open PR targeting the main branch
- All status checks have passed (all items in statusCheckRollup show "SUCCESS")
If no PR exists with passing tests, STOP and report: "No Pull Request ready to ship. A PR with passing tests is required."
2. Verify Version Bump Has Occurred
Compare the version in README.md and CLAUDE.md against the last tagged version:
# Get the last tagged version
git fetch --tags
git tag --sort=-v:refname | head -1
# Check versions in documentation files
grep -i "version" README.md
grep -i "version" CLAUDE.md
If the versions in README.md and CLAUDE.md match the last tagged version, STOP and report: "Version has not been bumped. Please update the version in README.md and CLAUDE.md before shipping."
3. Verify Version Consistency with App Store
Check that BOTH marketing version AND build number are correct:
# Check local marketing version (what users see: 1.0, 2.0, 22, 23...)
MARKETING_VERSION=$(grep "MARKETING_VERSION" <YourApp>.xcodeproj/project.pbxproj | head -1 | grep -oE '[0-9]+(\.[0-9]+)*')
echo "Local Marketing Version: ${MARKETING_VERSION}"
# Check local build number (internal counter: 274, 275, 276...)
BUILD_NUMBER=$(agvtool what-version -terse)
echo "Local Build Number: ${BUILD_NUMBER}"
CRITICAL: iOS apps use TWO version numbers:
- Marketing Version (user-visible): 1.0, 2.0, 22, 23... (shown in App Store)
- Build Number (auto-incrementing): 274, 275, 276... (internal tracking)
Verify on TestFlight:
- Marketing Version should match the version shown in TestFlight "Version" field
- Build Number must be HIGHER than the latest TestFlight build number
If marketing version doesn't match App Store, STOP and report: "Marketing version mismatch with App Store." If build number is not higher than App Store, STOP and report: "Build number must be incremented beyond App Store Connect."
4. Verify Changelog Entry Exists
Check that CHANGELOG.md has an entry for the current release in development:
# Get the version from project
VERSION=$(grep "MARKETING_VERSION" <YourApp>.xcodeproj/project.pbxproj | head -1 | grep -oE '[0-9]+(\.[0-9]+)*')
# Check if CHANGELOG.md has an entry for this version
grep -i "version ${VERSION}\|## ${VERSION}\|# ${VERSION}" CHANGELOG.md
If no changelog entry exists for the current version, STOP and report: "Missing changelog entry. CHANGELOG.md must have an entry for version ${VERSION} before shipping."
5. Determine Next Version
The next version is always one more than the last tag:
- If last tag is
v1.2.3, next version isv1.2.4 - If last tag is
1.2.3, next version is1.2.4(preserve format)
Shipping Process
Once all pre-flight checks pass:
Step 1: Merge the Pull Request
Squash merge to keep main branch history clean:
gh pr merge <PR_NUMBER> --squash --delete-branch
Step 2: Fetch the Merge Commit
git fetch origin main
git checkout main
git pull origin main
Step 3: Tag the Merge Commit
Tag the merge commit that was just created with the agreed-upon version:
git tag <VERSION>
git push origin <VERSION>
Step 4: Verify and Report
# Verify the tag exists on remote
git ls-remote --tags origin | grep <VERSION>
# Show the tagged commit
git show <VERSION> --oneline --no-patch
Report success with:
- The version number that was shipped
- The PR number that was merged
- A link to the new tag/release
Important Rules
- NEVER ship without a passing PR - Quality gates are mandatory
- NEVER ship if version hasn't been bumped - Each release must have a unique version
- NEVER ship if version doesn't match App Store canonical version - Version consistency is critical
- NEVER ship without a changelog entry - Users need to know what changed
- ALWAYS use squash merge - Keep main branch history clean with
--squashflag - ALWAYS increment from last tag - Version is always last tag + 1 (patch version)
- ALWAYS delete the branch after merge - Keep the repo clean
Failure Scenarios
| Scenario | Action |
|---|---|
| No open PR | Report "No PR ready to ship" |
| PR has failing tests | Report "Tests failing, cannot ship" |
| Version not bumped | Report "Version must be bumped before shipping" |
| Version doesn't match App Store | Report "Version mismatch with App Store canonical version" |
| Missing changelog entry | Report "CHANGELOG.md missing entry for current version" |
| Merge conflict | Report conflict and ask user to resolve |
| Tag already exists | Report error and verify correct version |