秋の新しい OS の見た目を先に確認したくて、ベータ版の Xcode を入れた月がありました。
壁紙アプリのレイアウトが新しい表示効果でどう崩れるかを見たかっただけです。ビルドは通り、TestFlight にも上がり、テスターの端末にも届きました。ここまでは順調でした。
詰まったのは、その同じビルドをそのまま審査に回そうとしたときです。アップロードは成功しているのに、提出の画面でそのビルドが選べません。
原因はコードではなく、ビルドの中に既に書き込まれていた SDK のバージョンでした。
TestFlight と審査では、受け付ける SDK が違います
先に結論を書きます。Apple はベータ版の SDK でビルドしたアプリを TestFlight の内部・外部テストに配ることを許可していますが、App Store 向けの提出には正式版(Release Candidate 以降)の SDK を求めています。
つまり「アップロードできた=提出できる」ではありません。App Store Connect はアップロードの入口と提出の入口で別々の判定をしていて、ベータ SDK のビルドは前者だけを通過します。
2026年8月20日時点の条件を整理すると、こうなります。
| 用途 | 必要な SDK | 現時点の例 |
|---|---|---|
| TestFlight の内部・外部テスト | ベータ SDK も可 | Xcode 27 beta 5 / iOS 27.0 beta 5 SDK が受付対象 |
| App Store の審査提出 | 正式版(RC 以降)の SDK | ベータ SDK のビルドは提出対象に出てきません |
| App Store Connect へのアップロード全般 | iOS 26 / iPadOS 26 SDK 以上 | 2026年4月28日から適用 |
3行目は見落としやすい条件です。「ベータを避ければいい」と考えて古い Xcode に戻すと、今度は最低要件のほうに引っかかります。上にも下にも壁がある、という理解が正確です。
なお iOS 27 SDK の必須化はまだ告知されていません。今すぐ Xcode 27 に上げる必要はない、というのが現時点の判断です。この手の条件は数か月単位で動くため、実際に提出する直前に App Store Connect のリリースノート と Xcode の SDK 要件 を開いて確認してください。
手元のビルドがどの SDK で作られたかを確認する
「たぶん正式版だったはず」で提出に進むと、審査の直前に足が止まります。確認は数秒で終わるので、習慣にしてしまうのが早いです。
判定材料は .ipa の中の Info.plist に入っています。ビルド時に Xcode が自動で書き込む項目で、後から手で書き換えるものではありません。
#!/usr/bin/env bash
# ipa-sdk.sh — .ipa がどの SDK / Xcode でビルドされたかを表示する
set -euo pipefail
IPA="${1:?usage: ipa-sdk.sh <path-to-ipa>}"
# .ipa は zip なので、Payload/*.app/Info.plist を展開せずに読み出します
PLIST_PATH="$(unzip -Z1 "$IPA" 'Payload/*.app/Info.plist' | head -1)"
unzip -p "$IPA" "$PLIST_PATH" \
| plutil -convert xml1 -o - - \
| grep -A1 -E '<key>(DTSDKName|DTXcode|DTPlatformBuild|MinimumOSVersion)</key>' \
| grep -v -- '--'実行するとこのような出力になります。
<key>DTPlatformBuild</key>
<string>23A5308c</string>
<key>DTSDKName</key>
<string>iphoneos27.0</string>
<key>DTXcode</key>
<string>2700</string>
<key>MinimumOSVersion</key>
<string>16.0</string>
読み方はこうです。DTSDKName がビルドに使われた SDK、DTXcode が Xcode のバージョン(2700 は 27.0)です。決め手になるのは DTPlatformBuild で、ビルド番号の末尾に小文字が付いているもの(上の例の 23A5308c)はベータ配布のビルドです。正式版になると末尾の小文字が消えます。
unzip -p で標準出力に流してから plutil に渡しているのは、.ipa を丸ごと展開したくないからです。数百 MB のファイルを毎回展開するのは、CI でも手元でも無駄が大きくなります。バイナリ形式の plist をそのまま grep しても読めないため、plutil -convert xml1 -o - を挟んでいます。
Xcode を選んでいるのは、Rork ではなく eas.json です
Rork でエクスポートしたプロジェクトを EAS Build に載せている場合、どの Xcode が使われるかは eas.json の image が決めています。ここを一度も触っていないなら、既定の auto が効いています。
image に指定できる値には別名(エイリアス)があり、意味がそれぞれ違います。
| 指定 | 意味 | 提出用ビルドでの扱い |
|---|---|---|
auto(未指定時の既定) |
その Expo SDK に見合ったイメージを EAS が選ぶ | 基本はこれで問題ありません |
latest |
最後に追加されたイメージを指す | ベータ Xcode のイメージが追加されると自動で追随します |
sdk-57 のような SDK 別名 |
特定の Expo SDK 向けのイメージ | 意図が読めるので、固定したいときに向きます |
注意したいのは latest です。名前から「安定版の最新」を想像しがちですが、実際には「直近で追加されたイメージ」であり、ベータ Xcode のイメージが公開された時点でそちらを指しにいきます。設定した本人が忘れた頃に、提出用ビルドがベータ SDK で作られる、という順序で起こります。
私が個人開発で使っている形は、提出用とベータ確認用を最初から分けておくやり方です。
{
"build": {
"production": {
"distribution": "store",
"ios": {
"image": "sdk-57"
}
},
"beta-preview": {
"distribution": "internal",
"channel": "beta-preview",
"ios": {
"image": "latest"
}
}
}
}production を SDK 別名で固定しておけば、新しいベータのイメージが追加されても提出用ビルドは動きません。新しい OS の見た目を確かめたいときは eas build --profile beta-preview を使い、こちらは内部配布に閉じておきます。
分けておく利点は、うっかりを防げることだけではありません。「今日のビルドはどちらの枠で作ったか」がプロファイル名で残るため、後から履歴を追うときに迷いません。
実際に使われた Xcode は、ビルドログの先頭付近に出るイメージ名で確認できます。設定した値と一致しているかを、最初の1回だけでも見ておくと安心です。
提出の直前に落とす仕組みを1つ入れておく
人の注意力に任せる部分は、いずれ抜けます。私は提出前の確認を1コマンドにまとめて、条件を満たさなければその場で止まるようにしています。
#!/usr/bin/env bash
# guard-release-sdk.sh — ベータ SDK のビルドを提出フローの手前で止める
set -euo pipefail
IPA="${1:?usage: guard-release-sdk.sh <path-to-ipa>}"
PLIST_PATH="$(unzip -Z1 "$IPA" 'Payload/*.app/Info.plist' | head -1)"
XML="$(unzip -p "$IPA" "$PLIST_PATH" | plutil -convert xml1 -o - -)"
read_key() {
echo "$XML" | grep -A1 "<key>$1</key>" | tail -1 | sed -E 's/.*<string>(.*)<\/string>.*/\1/'
}
SDK_NAME="$(read_key DTSDKName)"
PLATFORM_BUILD="$(read_key DTPlatformBuild)"
echo "SDK: ${SDK_NAME} / platform build: ${PLATFORM_BUILD}"
# ベータ配布のビルド番号は末尾に小文字が付きます(例: 23A5308c)
if [[ "$PLATFORM_BUILD" =~ [a-z]$ ]]; then
echo "審査には出せないビルドです。正式版 SDK で作り直してください。" >&2
exit 1
fi
echo "提出可能な SDK でビルドされています。"判定を DTPlatformBuild の末尾1文字に置いているのは、SDK 名だけでは足りないからです。iphoneos27.0 という文字列は、ベータでも正式版でも同じ値になります。ベータかどうかを分けているのはプラットフォームのビルド番号のほうです。
このスクリプトを提出前の手順の先頭に置いておくと、判断が「思い出す」から「実行する」に変わります。個人でリリースを回していると、確認の工程ほど省略の対象になりやすいので、止まる仕組みのほうを用意しておくのが現実的だと感じています。
TestFlight まで届いたのに公開後に挙動が変わる場合は、SDK ではなくビルド構成側の問題であることが多いです。その切り分けはTestFlight は問題ないのに App Store 公開後にクラッシュする4つの理由にまとめています。
今日のうちに見ておく場所
eas.json を開いて、提出用プロファイルの ios.image が latest になっていないかを確認してください。ここが latest のままなら、次にベータのイメージが追加された日に、提出できないビルドが静かに作られます。
一行の設定ですが、秋の OS 更新期に効いてくる場所です。私自身、上の一件があってから提出用の枠だけは固定するようになりました。
お読みいただきありがとうございました。同じところで足を止める方が一人でも減れば嬉しいです。