EAS Update を配信して、ダッシュボードの QR コードを iPhone の Expo Go で読むと、アプリが開きます。同じ QR コードを Android の Expo Go で読むと、赤い画面に1行だけ出て止まります。最初に見たとき、私なら更新データのほうを疑っていたと思います。
Uncaught Error: java.io.IOException: Failed to download remote update
「View error log」を開いても、中身はこの1行のままです。HTTP のステータスも、取れなかったアセットの名前も書かれていません。更新データを作り直し、キャッシュを消し、Expo Go を入れ直したあとで、もう一度同じ画面に戻ってくる——そういう夜を過ごしている方に向けて、報告されている原因と、手元での切り分けの順番を書き残します。
先に線を引きます。疑うのは更新データではなく Expo Go の側です
私も壁紙アプリを iOS と Android の両方で出しているので、片方だけ結果が違うと、まず自分の設定を疑う癖がついています。その癖に気づいたのは、同じ確認を何度も繰り返したあとでした。今回はその癖が裏目に出る種類の不具合でした。
Expo の GitHub に、まさにこの症状の報告があります(expo/expo#50139)。create-expo-app の空のテンプレートに expo-updates を足しただけの SDK 57 プロジェクトで、同じ更新グループが iOS では開き、Android 実機2台では開かなかったという内容です。依存は5つで、アセットは1つだけです。プロジェクト側に疑う余地がほとんど残っていません。
そこで、最初の切り分けは次の4点を、この順に見ます。
| 確かめること | この結果なら |
|---|---|
| 同じ更新が iOS の Expo Go で開くか | 開くなら、更新データ自体は健全です。Android だけの経路を疑います |
| Android が実機か、エミュレータか | エミュレータで開き、実機だけ開かないなら、今回の件に当てはまります |
| Expo Go のバージョン | SDK 57 向けの 57.0.9 で報告されています。58 系は後述のとおり事情が違います |
| サーバーが返す HTTP ステータス | 403 なら、読み込みの手前で弾かれています。404 なら、そもそも更新グループかランタイム版が違います |
最初の2行が「はい」なら、アセットや bundle を疑う前に、Expo Go が送っているリクエストの中身を疑う段階に入ってよいと思います。
原因は、サインインの情報が2か所に分かれていたことです
もう一つの報告(expo/expo#50253)が、ソースの該当行まで辿っています。要点は次のとおりです。
Android の Expo Go では、ホーム画面の作り直しのあと、サインインの秘密情報が expo_session という保存先に書かれるようになりました。ところが更新を読み込む側は、古い expo_auth_session という保存先を読み続けていました。後者を書き込んでいたのは、作り直しで消えた旧ホーム画面だけです。
その結果、プロフィールからサインインすると、ホーム画面にはプロジェクトとブランチの一覧がきちんと並びます。画面の上では認証されています。ところが、マニフェストとアセットを取りに行くリクエストには Expo-Session ヘッダーが付かないまま出ていきます。
サーバーが返すのは、報告によれば次の403です。
403: Experience "@owner/slug" is not viewable in Expo Go: this project requires authentication.
Sign in to Expo Go with an Expo account that is a member of the account that owns this project.
サインインしたあとで同じリンクを開き直しても、約0.6秒で同じ失敗が返ってくる、と報告者は書いています。修正の PR(expo/expo#50498)の説明には、この食い違いが長く見えなかった理由も添えられていました。サーバーがもともと所有者の確認を求めていなかったことと、エミュレータは EXPO_SIMULATOR として名乗るため、サーバーが確認の対象にしていないことの二つです。エミュレータでは動き、実機でだけ落ちる、という症状の説明にもなっています。
画面の上でサインインできていることと、読み込みが同じ鍵を見ていることは、別の話でした。
手元で確かめる2つの手順
一つめは、更新グループそのものがサーバーに見えているかを、プラットフォームごとに確かめる小さなスクリプトです。Expo Go を通さず、同じ URL に curl で問い合わせます。
#!/usr/bin/env bash
# 使い方: ./manifest_probe.sh <projectId> <groupId> <SDK のメジャー番号>
set -euo pipefail
PID="${1:?projectId}"; GID="${2:?groupId}"; SDK="${3:?sdk major}"
URL="https://u.expo.dev/${PID}/group/${GID}"
for P in ios android; do
CODE=$(curl -sS -m 20 -o "/tmp/probe_${P}.txt" -w '%{http_code}' "$URL" \
-H "expo-platform: ${P}" \
-H "expo-runtime-version: exposdk:${SDK}.0.0" \
-H "accept: multipart/mixed")
echo "${P}: HTTP ${CODE}"; head -c 300 "/tmp/probe_${P}.txt"; echo
done存在しない ID を渡して回したときの出力は、次のとおりでした。
ios: HTTP 404
There is no update identified by group ID "11111111-1111-4111-8111-111111111111" and platform "ios"
android: HTTP 404
There is no update identified by group ID "11111111-1111-4111-8111-111111111111" and platform "android"
読み方は単純です。両方が200なら、更新データはサーバー上で両プラットフォーム分そろっています。片方だけ404なら、そのプラットフォーム向けの eas update が通っていません。ここが両方そろっているのに Android の Expo Go だけ落ちるなら、更新データの問題ではなく、Expo Go が送るリクエストの問題です。なお、このスクリプトは Expo Go のサインイン情報を持たないので、403 を再現するためのものではありません。更新グループの有無とランタイム版の取り違えを先に除くための道具です。
二つめは、Android 実機で Expo Go のバージョンと、失敗の行を拾う手順です。
# Expo Go の版を確認し、読み込み失敗の行だけを拾う
adb shell dumpsys package host.exp.exponent | grep versionName
adb logcat -d | grep -i "Remote update request not successful"この行がログに出ていれば、サーバーがリクエストを受け取ったうえで、2xx 以外を返しています。先ほどの1行エラーに欠けていた「非2xx だった」という事実が、ここで初めて確かめられます。ただし、このコマンドは私の手元に Android 実機がないため実行しておらず、報告の記述に沿って書いたものです。
直るのはいつか。確認できた範囲だけを書きます
修正の PR は 2026-09-23 に main へ取り込まれました。この日付は、PR の状態から読み取った値です。そのうえで、ソースを実際に見比べた結果をお伝えします。
| ブランチ | 更新ヘッダーの読み出し元 |
|---|---|
| sdk-57 | 旧保存先(`exponentSharedPreferences.sessionSecret`)のまま |
| sdk-58 | 新しい保存先(`sessionRepository`)を読む形に変更済み |
SDK 57 向けの最後の Expo Go は 57.0.9(2026-08-15 公開)で、それより新しい 57 系のビルドは確認できませんでした。つまり、SDK 57 のまま Android の Expo Go で、所有者の確認が要る更新を開く経路は、今のところ直っていないと読めます。58 系の Expo Go(58.0.7 が 2026-10-24 公開)は、ソース上は修正を含んでいます。ただ、ストアから入る 58 系のビルドで実機の動作を確かめたわけではありません。ここは、確かめた範囲と確かめていない範囲を分けて書いておきます。
その間の逃げ道は、どれも「Expo Go の読み込み経路を通らない」か「通る経路を変える」ものです。
- iOS の Expo Go で確認します。報告でも、同じ更新が iOS では開いています。
- Android のエミュレータで確認します。PR の説明によれば、エミュレータはサーバーの確認の対象外です。
- Expo Go ではなく、自分でビルドしたアプリ(development build や内部配布のビルド)に更新を当てて確認します。不具合は Expo Go の更新ローダーの中にあるため、この経路は通りません。
- SDK 58 へ上げたうえで、58 系の Expo Go で試します。移行の量が大きいので、このためだけに上げるのは勧めません。
どれを選ぶかは、お手元の状況しだいです。ひとまず「更新データは壊れていない」と分かっただけで、作り直しと再配信の往復は止められます。
更新が反映されない別のパターンを切り分けたい方は、EAS Update を配信したのに反映されないときの5つの典型パターン もあわせてご覧ください。Expo Go 側のログインそのものを確かめる順番は、Expo Go がプレビューを開かなくなった日に最初に確かめる2つのログイン に書いてあります。
次に Android だけが違う結果を返したら、更新データを作り直す前に、iOS で開くかどうかと、実機かエミュレータかの2点を確かめてみていただければと思います。この2点は、片方のプラットフォームだけ結果が違うときの確認手順の先頭に置いておこうと、いまは考えています。