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.
This commit is contained in:
Johannes Millan 2026-07-02 16:02:52 +02:00
parent 2261160436
commit 429dc4c7e9

View file

@ -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