Troubleshooting

Symptom, cause, and fix for the failures you're most likely to hit — by area.

Symptom → cause → fix for the problems that actually come up building on this template, grouped by area. Each item links back to the page that owns the subject. App-Store-specific purchase failures live here in condensed form; the full RevenueCat/StoreKit matrix is in Purchase Testing.

Build & tooling

SPM packages won't resolve / red package errors on open. Xcode couldn't fetch dependencies (Kingfisher, supabase-swift, purchases-ios, posthog-ios, sentry-cocoa). File → Packages → Reset Package Caches, then Resolve Package Versions. Confirm network access to GitHub. See Installation.

xcodebuild uses the wrong Xcode / SDK not found. The command line points at a different Xcode than the app you opened. Set it explicitly:

DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcodebuild \
  -scheme SoarStarterSwift \
  -destination 'platform=iOS Simulator,name=iPhone 17 Pro' build

or sudo xcode-select -s /Applications/Xcode.app. Requires Xcode 16+.

Tests fail intermittently / flaky when run together. The suite is written to run serially (shared state reset per test). Always disable parallel testing:

xcodebuild test -scheme SoarStarterSwift \
  -destination 'platform=iOS Simulator,name=iPhone 17 Pro' \
  -parallel-testing-enabled NO

See Architecture → Testing.

Secrets & configuration

A key I set in Secrets.xcconfig isn't taking effect. The value has to flow all the way through the chain: Secrets.xcconfigInfo.plist $() substitution → AppSecrets. Check that (a) Secrets.xcconfig is #include?-d, (b) the key exists in Info.plist with the $(KEY) placeholder, and (c) you did a clean build (edits to xcconfig don't always invalidate the cache — Product → Clean Build Folder). See Configuration.

A URL value in xcconfig is mangled / truncated. In xcconfig // starts a comment, so https://… loses everything after the //. Use the template's escaping trick — write the value as https:/$()/… so the $() breaks up the //. See Configuration.

A service does nothing but the app doesn't crash. That's by design. Every secret is optional; a missing key makes the owning controller report isConfigured = false and no-op. If a feature is silently inert, its key probably isn't set — check the key reference.

Supabase & auth

OAuth / email OTP dead-ends or never returns to the app. The redirect isn't in Supabase's allow-list, or the URL scheme isn't registered. Add soar://auth-callback to Authentication → URL Configuration → Redirect URLs, and confirm the soar scheme is in Info.plist CFBundleURLTypes. See Supabase Setup.

Google Sign-In opens then fails immediately. The reversed client ID URL scheme is missing or wrong. GOOGLE_REVERSED_CLIENT_ID must be registered as a URL scheme and match the one from your GOOGLE_IOS_CLIENT_ID. See Authentication.

Signed in, but queries return nothing / RLS denies. The session is valid but the row-level policy rejects the read (e.g. querying todos while signed out, or a table without the expected policy). Confirm the session exists and the table's RLS matches the app's expectations — the Data Layer lists per-table policies.

Session doesn't persist across launches / logs out unexpectedly. supabase-swift stores the session in the Keychain (KeychainLocalStorage), not UserDefaults. If you swapped storage or reset the Keychain during testing, re-authenticate. Don't move the session to UserDefaults. See Authentication.

Push notifications

No device token on the simulator. Expected — the simulator never registers for remote push. APNs is Device-only: you need a real device and a paid Apple team. See Push Notifications.

BadDeviceToken when sending a test push. Sandbox vs production APNs mismatch — a token minted in one environment sent to the other. Match the endpoint to the build (aps-environment); the send-test-push function prunes BadDeviceToken / Unregistered rows from device_tokens. See Push Notifications.

Monetization

Paywall is empty / "Couldn't load license options." offerings() returned no current offering or no packages. Walk the chain: appl_ SDK key, an active offering with a package, the package attached to the right App Store product, the product attached to the entitlement, product no longer Missing Metadata, and agreements/tax/banking complete. Full checklist in Purchase Testing.

TestFlight/Release build crashes on launch (checkForSimulatedStoreAPIKeyInRelease). REVENUECAT_IOS_KEY is a simulated-store key (test_) in a Release build. Replace it with the App Store public SDK key (appl_), bump the build, re-upload. See App Store Release.

Entitlement doesn't unlock after a successful purchase. REVENUECAT_PRO_ENTITLEMENT_ID doesn't match the RevenueCat entitlement identifier (not display name; template default pro). Also confirm the webhook reached Supabase and wrote the subscriptions row — the app reads that table, not CustomerInfo, as the source of truth. See Subscriptions.

Missing Compliance on the TestFlight build. Export-compliance info is needed. If the app only uses standard SDK networking, declare no non-exempt encryption (or set ITSAppUsesNonExemptEncryption to false in Info.plist). See App Store Release.

The update gate blocks me while developing. MIN_APP_VERSION is above the running build, forcing the update screen. To test the gate deliberately, bump MIN_APP_VERSION / LATEST_APP_VERSION in Secrets.xcconfig; to get past it, lower them. See App Gates.

A deep link doesn't open the right screen. Check the capture channel and the route. Custom scheme links (soar://…) need the scheme in CFBundleURLTypes; Universal Links (https://…) need the applinks: entitlement pointing at your LINKING_DOMAIN with a hosted AASA file. Test in the simulator with:

xcrun simctl openurl booted "soar://todos"

See Deep Links.

Consent gate keeps reappearing. The stored consent version no longer matches LegalContent.version (a dated string) — bumping it intentionally re-prompts everyone. If it's unexpected, you changed LegalContent.version. See App Gates.

Still stuck?

Each section links to the page that owns the subject — start there for the full explanation. For sandbox purchase issues specifically, the Purchase Testing page has the complete RevenueCat/StoreKit failure matrix (error code 23, "Could not check", "Already purchased", and more).

On this page