ai/ios-skills/ios-axiom-app-store-diag/SKILL.md
Use when app is rejected by App Review, submission blocked, or appeal needed - systematic diagnosis from rejection message to fix with guideline-specific remediation patterns and appeal writing
npx skillsauth add kurko/dotfiles axiom-app-store-diagInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Systematic App Store rejection diagnosis and remediation. Covers the 6 most common rejection categories that account for 90% of all App Review failures.
Core principle 90% of App Store rejections fall into 6 categories. Reading the rejection message carefully and mapping to the correct guideline prevents the #1 mistake: fixing the wrong thing and getting rejected again for the same reason.
Most developers waste 1-2 weeks on rejection cycles because they skim the rejection message, assume the cause, and "fix" something that wasn't the problem. This skill provides systematic diagnosis from rejection message to targeted fix.
If you see ANY of these, suspect a submission issue and use this skill:
Rejection message cites a specific guideline number
"Binary Rejected" without clear guideline (technical gate failure)
Same app rejected multiple times for different reasons
"Metadata Rejected" (no code change needed)
Rejection mentions "privacy" or "data collection"
Rejection mentions "login" or "authentication"
Reviewer asks for demo account or more information
❌ FORBIDDEN "The reviewer is wrong, let's just resubmit"
ALWAYS do these BEFORE changing any code:
| Rejection Type | What Changed | Next Step | |---|---|---| | "App Rejected" + Guideline 2.1 | App crashed or had placeholders | Pattern 1 | | "Metadata Rejected" | Screenshots or description wrong | Pattern 2 | | "App Rejected" + Guideline 5.1 | Privacy policy or manifest gaps | Pattern 3 | | "App Rejected" + Guideline 4.8 | Missing Sign in with Apple | Pattern 4 | | "App Rejected" + Guideline 3.x | Business/monetization violation | Pattern 5 | | "Binary Rejected" / no guideline | SDK, signing, or encryption issue | Pattern 6 | | Reviewer seems incorrect | Genuine misunderstanding | Pattern 7 | | Guideline 1.x cited | Safety/content issue | Content review needed | | Guideline 4.1-4.3 cited | Design/originality issue | Functionality review needed |
Before changing ANY code, identify ONE of these:
App Store rejection?
│
├─ What does the rejection say?
│ │
│ ├─ Cites Guideline 2.1?
│ │ ├─ App crashed during review? → Pattern 1 (check crash logs)
│ │ ├─ Placeholder content found? → Pattern 1 (search project)
│ │ ├─ Broken links? → Pattern 1 (verify URLs)
│ │ └─ Missing demo credentials? → Pattern 1 (provide in review notes)
│ │
│ ├─ Cites Guideline 2.3?
│ │ ├─ Screenshots don't match app? → Pattern 2 (retake screenshots)
│ │ ├─ Description promises missing features? → Pattern 2 (update text)
│ │ └─ Keywords contain trademarks? → Pattern 2 (remove keywords)
│ │
│ ├─ Cites Guideline 5.1?
│ │ ├─ Privacy policy missing/inaccessible? → Pattern 3 (add/fix policy)
│ │ ├─ Purpose strings missing? → Pattern 3 (add to Info.plist)
│ │ ├─ Privacy manifest incomplete? → Pattern 3 (update PrivacyInfo)
│ │ └─ Tracking without ATT? → Pattern 3 (implement ATT)
│ │
│ ├─ Cites Guideline 4.8?
│ │ ├─ Third-party login without SIWA? → Pattern 4 (add SIWA)
│ │ ├─ SIWA button hidden or broken? → Pattern 4 (fix prominence)
│ │ └─ Exception applies? → Pattern 4 (verify exemption)
│ │
│ ├─ Cites Guideline 3.x?
│ │ ├─ Digital content without IAP? → Pattern 5 (implement StoreKit)
│ │ ├─ Subscription issues? → Pattern 5 (fix terms/value)
│ │ └─ Loot box odds not disclosed? → Pattern 5 (add disclosure)
│ │
│ ├─ "Binary Rejected" / no guideline?
│ │ ├─ Wrong SDK version? → Pattern 6 (update Xcode)
│ │ ├─ Privacy manifest missing? → Pattern 6 (add PrivacyInfo)
│ │ ├─ Encryption not declared? → Pattern 6 (add ITSAppUsesNonExemptEncryption)
│ │ └─ Invalid signing? → Pattern 6 (regenerate provisioning)
│ │
│ ├─ "I believe the reviewer is wrong"?
│ │ └─ → Pattern 7 (Appeal Process)
│ │
│ ├─ Cites Guideline 1.x?
│ │ └─ Safety/content issue → Review content against guideline
│ │
│ └─ Cites Guideline 4.1-4.3?
│ └─ Design/originality issue → Review app uniqueness/functionality
Before proceeding to a pattern:
Time cost 3-7 days per rejection cycle
# 1. Check crash logs in App Store Connect
# Xcode Organizer > Crashes > Filter by version
# 2. Search for placeholder strings
grep -r "Lorem\|TODO\|FIXME\|placeholder\|sample\|test data" \
--include="*.swift" --include="*.storyboard" --include="*.xib" .
# 3. Verify all URLs resolve
curl -sI "https://your-support-url.com" | head -1
curl -sI "https://your-privacy-policy-url.com" | head -1
# 4. Test on latest shipping iOS
# Check ASC for specific iOS version reviewer used (noted in rejection)
// ❌ WRONG — Demo credentials that expire
// Review Notes: "Login: [email protected] / password123"
// (If this account expires or gets locked, instant rejection)
// ✅ CORRECT — Permanent demo credentials
// Review Notes:
// "Demo Account: [email protected] / ReviewDemo2024!
// This account has pre-populated sample data.
// Account will not expire during review period."
// ❌ WRONG — Placeholder still in code
Text("Lorem ipsum dolor sit amet")
// ✅ CORRECT — Real content in every screen
Text("Welcome to YourApp. Get started by creating your first project.")
Time cost 1-3 days (metadata fix, no build needed)
Compare every screenshot to current app UI. Read description word by word — does each claim exist in the app? Check keywords against Apple's trademark list.
Checklist:
☐ Every screenshot matches current build
☐ Every feature mentioned in description exists and works
☐ No trademarked terms in keywords (e.g., "Instagram", "Uber")
☐ App icon appropriate for all audiences
☐ Age rating matches actual content
☐ Category selection accurate
☐ "What's New" text matches actual changes
Update metadata directly in App Store Connect. No new build needed for metadata-only rejections.
✅ Take fresh screenshots FROM THE SUBMITTED BUILD (not dev build)
✅ Remove any features from description that aren't fully functional
✅ Replace trademarked keywords with generic equivalents
("photo sharing" not "Instagram-like")
✅ Ensure "What's New" describes changes in this specific version
Time cost 3-10 days (code + manifest + policy changes)
// 1. Check: Is privacy policy URL in ASC AND accessible from within the app?
// Both are required. In-app access is commonly missed.
// 2. Check purpose strings
// ❌ WRONG — Generic purpose string
"NSCameraUsageDescription" = "Camera access needed"
// ✅ CORRECT — Specific purpose string explaining why
"NSCameraUsageDescription" = "Take photos for your profile picture and upload to your account"
// 3. Generate privacy report
// Xcode: Product → Archive → Generate Privacy Report
// This shows aggregate data from all frameworks and your code
// 4. Check privacy manifest
// Verify PrivacyInfo.xcprivacy exists in your app target
// AND in every framework target that uses required reason APIs
<!-- Every permission MUST have a specific, honest purpose string -->
<key>NSCameraUsageDescription</key>
<string>Take photos for your profile picture and upload to your account</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Show nearby restaurants on the map and calculate delivery distance</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Select photos from your library to attach to messages</string>
<!-- Required if you use any "required reason" APIs -->
<!-- UserDefaults, file timestamp, disk space, system boot time, etc. -->
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
</array>
</dict>
Your privacy policy MUST specifically list:
☐ What data is collected (every type)
☐ How data is collected (automatically, user-provided)
☐ All uses of collected data
☐ Third-party sharing (who, why)
☐ Data retention period
☐ How users can request deletion
☐ Contact information for privacy inquiries
// Required if app tracks users across other companies' apps/websites
import AppTrackingTransparency
func requestTrackingPermission() {
ATTrackingManager.requestTrackingAuthorization { status in
switch status {
case .authorized:
// Enable tracking (analytics, ad attribution)
break
case .denied, .restricted, .notDetermined:
// Disable ALL tracking
// Remove IDFA access, disable third-party analytics that track
break
@unknown default:
break
}
}
}
Time cost 3-7 days (implementation + resubmit)
The rule is simple: If your app uses ANY third-party or social login service, you MUST offer Sign in with Apple as an equivalent option.
Exceptions (SIWA not required):
import AuthenticationServices
// ✅ CORRECT — SIWA at same prominence as other login options
struct LoginView: View {
var body: some View {
VStack(spacing: 16) {
// Sign in with Apple — MUST be at same visual level
SignInWithAppleButton(.signIn) { request in
request.requestedScopes = [.fullName, .email]
} onCompletion: { result in
switch result {
case .success(let authorization):
handleAuthorization(authorization)
case .failure(let error):
handleError(error)
}
}
.signInWithAppleButtonStyle(.black)
.frame(height: 50)
// Other login options at same size/prominence
GoogleSignInButton()
.frame(height: 50)
}
}
func handleAuthorization(_ authorization: ASAuthorization) {
guard let credential = authorization.credential
as? ASAuthorizationAppleIDCredential else { return }
let userIdentifier = credential.user
let fullName = credential.fullName
let email = credential.email
// Note: fullName and email only provided on FIRST sign-in
// Store them immediately — they won't be provided again
// Send to your backend for account creation/login
}
}
// ✅ Handle credential revocation (required for account deletion support)
func checkCredentialState() {
let provider = ASAuthorizationAppleIDProvider()
provider.getCredentialState(forUserID: storedUserIdentifier) { state, error in
switch state {
case .authorized:
break // User is still signed in
case .revoked:
// User revoked credentials — sign out immediately
signOut()
case .notFound:
// Credential not found — show sign-in
showLogin()
@unknown default:
break
}
}
}
Time cost 3-14 days (may require architectural changes)
The key question: Is any digital content or feature unlocked without Apple IAP?
Digital goods/features → MUST use Apple IAP
Examples: premium features, virtual currency, ad removal, content
packs, subscription access to digital content
Physical goods/services → MAY use external payment
Examples: physical merchandise, ride-sharing, food delivery,
person-to-person services
Certain categories → MAY use external payment (3.1.3 exceptions)
Examples: "reader" apps (Kindle, Netflix, Spotify), one-to-one
real-time services
// ❌ WRONG — Unlocking features via external payment
func unlockPremium(receiptFromServer: String) {
// Bypass Apple IAP → rejection
UserDefaults.standard.set(true, forKey: "isPremium")
}
// ✅ CORRECT — StoreKit 2 for all digital goods
import StoreKit
func purchasePremium() async throws {
let product = try await Product.products(for: ["com.app.premium"]).first!
let result = try await product.purchase()
switch result {
case .success(let verification):
let transaction = try checkVerified(verification)
// Unlock feature
await transaction.finish()
case .pending:
// Payment pending (Ask to Buy, etc.)
break
case .userCancelled:
break
@unknown default:
break
}
}
// ✅ Loot box disclosure (required if random items for purchase)
struct LootBoxView: View {
var body: some View {
VStack {
Text("Mystery Box — $4.99")
Text("Contents are random. Odds:")
.font(.caption)
// MUST disclose odds before purchase
VStack(alignment: .leading) {
Text("Common item: 60%")
Text("Rare item: 30%")
Text("Legendary item: 10%")
}
.font(.caption2)
.foregroundStyle(.secondary)
}
}
}
Time cost 1-3 days (build configuration fix)
# 1. Check Xcode and SDK version
xcodebuild -version
# Must be current or previous major Xcode version
# 2. Check processing logs in ASC
# App Store Connect → My Apps → [App] → Activity → Build → Processing Log
# 3. Verify encryption declaration
grep -c "ITSAppUsesNonExemptEncryption" Info.plist
# Must exist and be set to YES or NO
# 4. Check provisioning
security cms -D -i embedded.mobileprovision 2>/dev/null | head -20
# Verify not expired
# 5. Check for private API usage
# Xcode: Product → Archive → Distribute App → Validate App
# This catches most private API issues before submission
<!-- Encryption compliance (Info.plist) -->
<!-- If app uses ONLY standard HTTPS (URLSession, etc.) -->
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<!-- If app uses custom encryption beyond HTTPS -->
<key>ITSAppUsesNonExemptEncryption</key>
<true/>
<!-- Then upload export compliance documentation in ASC:
App Store Connect → My Apps → [App] → App Information →
Export Compliance Information → Upload documentation
You may also need to file an annual self-classification
report with the US Bureau of Industry and Security (BIS) -->
Encryption decision flow:
false, donetrue, upload BIS docstrue, upload BIS docsstrings YourApp | grep -i "openssl\|libcrypto\|CCCrypt" to check# Validate before submitting
# Xcode: Product → Archive → Distribute App → Validate App
# Catches ~80% of binary rejection causes
# Clean build if signing issues
rm -rf ~/Library/Developer/Xcode/DerivedData
# Re-download provisioning profiles in Xcode Preferences → Accounts
Time to resolve 3-14 days
Most issues resolve without a formal appeal. Reply to App Review messages in ASC with:
URL: developer.apple.com/contact/app-store/?topic=appeal
✅ GOOD appeal structure:
"Our app complies with Guideline [X.Y] because [specific evidence].
The reviewer noted: '[quote exact rejection text]'
However, our app [specific counter-evidence with details]:
1. [Feature X] works as shown in [attached screenshot/video]
2. [Policy Y] is accessible at [URL] and within the app at [screen]
3. [Requirement Z] is implemented as described in [technical detail]
Attached: [screenshots, screen recording, or documentation]
We respectfully request re-review of this decision."
❌ BAD appeal examples:
"This is unfair. Other apps do the same thing. Please approve."
→ Apple reviews each app independently
"We've been rejected 3 times and are losing money."
→ Financial pressure is not relevant to guideline compliance
"The reviewer didn't understand our app."
→ Vague. Show specifically what they missed.
"We need this approved by Friday for our launch."
→ Deadlines are not App Review's concern
If appeal is denied:
| Rejection Type | Likely Cause | First Check | Pattern | Typical Fix Time | |---|---|---|---|---| | Guideline 2.1 | Crashes/placeholders | Test on device, search placeholders | 1 | 1-3 days | | Guideline 2.3 | Metadata mismatch | Compare screenshots to app | 2 | 1 day (no build) | | Guideline 5.1 | Privacy gaps | Check policy + manifest + purpose strings | 3 | 2-5 days | | Guideline 4.8 | Missing SIWA | Check for third-party login | 4 | 3-5 days | | Guideline 3.x | Payment method | Review IAP flows | 5 | 3-14 days | | Binary Rejected | Technical gate | Check SDK, manifest, encryption | 6 | 1-2 days | | Guideline 1.x | Safety/content | Review content policy | N/A | Varies | | Guideline 4.1-4.3 | Design/originality | Review app uniqueness | N/A | Varies |
Situation: Marketing committed to a launch date. App was rejected for crashes (fixed), then metadata (fixed), now privacy policy "doesn't match actual data collection."
Pressure signals:
Why this happens: Each review pass goes deeper. First pass catches obvious issues (crashes). Second pass checks metadata. Third pass audits privacy compliance. This is normal, not "the reviewer is picking on you."
"Just fix the privacy policy wording and resubmit"
"The reviewer is being unreasonable, let's appeal"
"Let's remove the privacy-sensitive features to ship faster"
"Different reviewer next time might not notice"
| Approach | Time to Approval | |---|---| | Quick fix + resubmit | 7-14 more days (likely rejected again) | | Full audit + thorough fix | 3-5 days (high confidence) | | Full audit + expedited review | 1-3 days (if granted) |
To stakeholders:
"Root cause: Our third-party analytics SDK collects device identifiers
that weren't disclosed in our privacy policy or nutrition labels.
Fix: Updated privacy policy, privacy nutrition labels in ASC, and
PrivacyInfo.xcprivacy to accurately reflect all data collection.
Also audited all SDKs for undisclosed collection.
Timeline: Resubmitting today with expedited review request.
Expected approval: 1-3 business days.
Prevention: Adding privacy audit to our pre-submission checklist
so future submissions include accurate disclosure from the start."
Problem Developer reads "Guideline 5.1" and assumes they know the issue without reading the full explanation.
Why it fails Guideline 5.1 covers privacy policy, purpose strings, privacy manifest, tracking, AND data collection disclosure. The rejection message tells you exactly which aspect failed. Guessing the wrong one wastes a full review cycle (3-7 days).
Fix: Copy the FULL rejection text. Highlight every specific requirement mentioned. Map each one to the fix before writing any code.
Problem Rejection cites Guideline 5.1 (privacy). Developer fixes privacy but doesn't check for other issues.
Why it fails Reviewers find new issues on each pass. First pass catches crashes, second catches metadata, third catches privacy. If you only fix privacy, the fourth pass might find a Guideline 4.8 (SIWA) issue.
Fix: Before every resubmission, run through ALL common rejection patterns (1-6). Fix everything proactively. One thorough submission beats three partial ones.
Problem "Maybe a different reviewer will approve it."
Why it fails Reviewers see the rejection history. Unchanged resubmissions get the same result or escalated to senior reviewers. Each wasted cycle costs 3-7 days.
Fix: Always make at least the changes the reviewer requested. If you believe the rejection is wrong, reply in ASC with evidence first.
Problem "This is unfair! Other apps do this! You're blocking our business!"
Why it fails App Review is a technical compliance review, not a negotiation. Emotional arguments are ignored. Specific evidence of compliance works.
Fix: Be factual, specific, and professional. Quote the guideline. Show screenshots. Provide technical evidence.
Problem "We don't collect that data — it must be the SDK."
Why it fails Your app is responsible for ALL SDK behavior. If Facebook SDK collects device identifiers, YOUR privacy policy and nutrition labels must disclose it.
Fix: Audit every third-party SDK. Generate Privacy Report to see aggregate data collection. Update privacy policy and nutrition labels to cover all SDK behavior.
Problem Pushing a backend update that changes API responses while the app is under review.
Why it fails Reviewers may test at any time during the review window. A backend change that breaks the reviewed build = crash during review = Guideline 2.1 rejection.
Fix: Freeze backend during review period. If changes are necessary, ensure backward compatibility with the submitted build.
Problem Developer doesn't know about or doesn't use expedited review for critical situations.
Why it fails Waiting 3-7 days for standard review when a 1-day expedited review is available for legitimate reasons.
Fix: Request expedited review at developer.apple.com/contact/app-store/?topic=expedite for: critical bug fixes, time-sensitive events, or security patches. Don't abuse it — Apple tracks usage and may deny future requests.
Run through this BEFORE every App Store submission to prevent rejections:
App Completeness (2.1):
☐ Tested on latest shipping iOS version on physical device
☐ Tested on at least 2 device sizes (iPhone SE, iPhone Pro Max)
☐ No placeholder text (search: Lorem, TODO, FIXME, placeholder, sample)
☐ All URLs resolve (support URL, privacy policy, in-app links)
☐ Demo credentials provided if login required (non-expiring)
☐ Backend stable and not deploying during review window
Metadata (2.3):
☐ Screenshots taken from submitted build (not dev build)
☐ Every feature in description exists and works
☐ No trademarked terms in keywords
☐ Age rating matches content
☐ "What's New" text accurate
Privacy (5.1):
☐ Privacy policy accessible in-app AND via URL in ASC
☐ Privacy policy matches actual data collection
☐ Every permission has specific, honest purpose string
☐ PrivacyInfo.xcprivacy exists and lists all required reason APIs
☐ Privacy Report generated and cross-referenced
☐ ATT implemented if any cross-app tracking
☐ Privacy nutrition labels accurate (including third-party SDKs)
Sign in with Apple (4.8):
☐ If third-party login exists, SIWA offered at same prominence
☐ SIWA flow works: sign in, account creation, revocation handling
☐ Account deletion supported (required since June 2022)
Business (3.x):
☐ All digital goods/features use Apple IAP
☐ IAP products approved in ASC before app submission
☐ Subscription terms clear before purchase
☐ Loot box odds disclosed if applicable
Technical (Binary):
☐ Xcode version meets Apple's current requirements
☐ "Validate App" passes in Xcode Organizer
☐ ITSAppUsesNonExemptEncryption key present
☐ Provisioning profile not expired
☐ Tested with release configuration on device
WWDC: 2025-328
Docs: /app-store/review/guidelines, /distribute/app-review, /support/offering-account-deletion-in-your-app, /contact/app-store/?topic=appeal
Skills: app-store-connect-ref, privacy-ux, storekit-ref, accessibility-diag
data-ai
Merge the current worktree branch into main and sync main back. Use when the user says "merge to main", "ship it", "merge and continue", or after completing a task in a worktree and wanting to continue with the next one.
tools
Synchronize AI agent skills, commands, configs, permissions, hooks, and instructions across Claude Code, Codex CLI, and other Agent Skills-compatible tools. Use when the user asks to pull skills from Claude into Codex, sync Codex work back to Claude, migrate agent commands, reconcile frontmatter, update permissions, or keep agent setup files in parity.
testing
Write or update UI-independent use cases for QA. Use when the user says "write use cases", "add use cases", "QA use cases", "update use cases", "compose use cases", or when starting implementation of a new feature (after plan approval). Also activates for "what should we test", "regression cases", or "use cases for QA".
documentation
Skill on how to write a task. Use when user asks you to write a task (for Asana, Linear, Jira, Notion and equivalent). Also activates when user says "create task", "write task", or similar task creation workflow requests.