From 429dc4c7e95e87eeaa4a6d9787701d49e94c954b Mon Sep 17 00:00:00 2001 From: Johannes Millan Date: Thu, 2 Jul 2026 16:02:52 +0200 Subject: [PATCH] ci(release): tolerate concurrent App Store review-submission race The iOS and macOS release lanes both run on a v* tag push and can race on App Store Connect's per-app review-submission state. When they collide, the lane that submits second fails with 'A review submission is already in progress' -- but only after its binary has already uploaded and processed successfully, so the build is safe. Route both lanes through a shared submit_to_app_store helper that treats that one collision as a soft success and emits a GitHub Actions warning annotation so the required manual step (add the build to the open submission) stays visible on a green run. Every other failure, and any failure before upload (e.g. the version-creation race), still aborts the lane loudly. --- fastlane/Fastfile | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index f37f59b727..a87b6f0e0e 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -81,16 +81,52 @@ def deliver_options(extra) }.merge(extra) end +# Both release lanes run on the same `v*` tag push (see the two release +# workflows) and can race on App Store Connect's per-app review-submission +# state. When they collide, the lane that submits second gets "Cannot submit +# for review - A review submission is already in progress" -- but only AFTER its +# binary has already uploaded and processed successfully, so the build is safe +# on App Store Connect. Treat that one collision as a soft success and emit a CI +# warning: the build just needs to be added to the already-open submission for +# THIS version in the App Store Connect UI (otherwise this platform skips this +# version and users get it at the next release). Every other failure -- and any +# failure before the binary uploads, e.g. the version-creation race -- still +# aborts the lane loudly. +# +# No stable error code is surfaced through deliver, so this is message-matched +# (verified against fastlane deliver 2.225.0). If Apple/fastlane rephrase it, +# the match stops firing and the lane falls back to hard-failing -- the safe +# direction. +REVIEW_SUBMISSION_RACE = 'review submission is already in progress' + +def submit_to_app_store(extra) + upload_to_app_store(deliver_options(extra)) +rescue => e + raise unless submit_for_review? && e.message.to_s.downcase.include?(REVIEW_SUBMISSION_RACE) + # Surface as a GitHub Actions run annotation, not just a buried log line, so a + # green job still flags the required manual step. Static text only -- never + # interpolate e/options here (they can carry API key material; see header). + if ENV['GITHUB_ACTIONS'] + puts '::warning title=App Store build uploaded but NOT submitted::A review ' \ + 'submission is already open for this app. Add this build to it in App ' \ + 'Store Connect, or this platform skips this version.' + end + UI.important('Build uploaded, but a review submission is already open for this app') + UI.important("(the other platform's release lane created it first). Add this build to") + UI.important('the open submission in App Store Connect for this version, or this platform') + UI.important('will skip this version. Not failing the lane: the binary is uploaded.') +end + platform :ios do desc 'Upload the prebuilt iOS .ipa to App Store Connect and submit for review' lane :release do - upload_to_app_store(deliver_options(platform: 'ios', ipa: ENV.fetch('IPA_PATH'))) + submit_to_app_store(platform: 'ios', ipa: ENV.fetch('IPA_PATH')) end end platform :mac do desc 'Upload the prebuilt macOS .pkg to App Store Connect and submit for review' lane :release do - upload_to_app_store(deliver_options(platform: 'osx', pkg: ENV.fetch('PKG_PATH'))) + submit_to_app_store(platform: 'osx', pkg: ENV.fetch('PKG_PATH')) end end