Some nights the Android build log is quiet for a hundred lines and then ends in three red ones.
Could not resolve project :react-native-async-storage_async-storage
> No matching variant of project :react-native-async-storage_async-storage was found.
> No variants exist.
"No variants exist" gives you nothing to hold on to. You doubt the dependency declarations, then the Gradle version, and the date changes before anything is fixed.
This time I read through the reports filed on Expo's GitHub and tried to trace where the symptom comes from. I should say up front that I did not reproduce it myself. As an indie developer who works on a Mac, I have never hit it. What I did instead was arrange the order in which the reporters found things into something you can check on your own project before you build.
The reported symptom: it passes locally and fails only on EAS
The original report is an Expo SDK 54 project (54.0.31). The Android Gradle build failed only on EAS Build, with the message above.
What stands out is what the reporter confirmed afterwards. Running gradlew assembleRelease on the same project on a Windows machine succeeded. Same code, same SDK, different result.
In that situation it is tempting to blame dependency resolution. But if the build passes locally, the declarations themselves are probably fine. The difference lives in what changed between your machine and the builder.
The cause that was found: a cache made on another OS
Later in the same thread, someone wrote that the cause had been identified. Summarizing what they quoted as Expo support's answer: a Windows-generated build cache was included in the android folder, and it did not exist on the Linux builder.
The file in question is android/build/generated/autolinking/autolinking.json. Because it was generated on Windows, module locations were written as Windows absolute paths. The file came back to the builder through EAS's cache restoration, and each module's projectDir pointed to a place that does not exist on Linux. The Android plugin was never applied, so no variants were produced. "No variants exist" was describing exactly that state.
To be careful here: this explanation comes from one reporter quoting support. It is not something I could confirm in the official documentation, so I treat it as a strong hypothesis.
Another reporter fixed the same symptom differently. They corrected what expo-doctor flagged, aligned duplicated dependencies with npm dedupe, and rebuilt with eas build --clear-cache. That suggests the same message can have more than one cause.
| Reported remedy | Likely cause it addresses | Effort |
|---|---|---|
Fix what expo-doctor flags | Config or dependency mismatch | Small |
Align dependencies with npm dedupe | Mixed versions of the same package | Small |
eas build --clear-cache | A stale cache being restored | Small (the build gets longer) |
| Stop the autolinking cache from being used | A file containing another OS's paths | Medium |
Trying them from the top is slower in appearance and faster in practice, in my experience.
Check your android/build before you queue the build
If you only notice after waiting, one whole build's worth of time is gone. So here is a script that inspects android/build before you send anything. It checks three things: whether Git tracks the file, whether it is ignored, and whether the JSON contains Windows-style paths.
#!/usr/bin/env bash
# Checks that the autolinking cache under android/build does not carry
# another OS's paths up to EAS Build. Run it before you queue a build.
set -u
F="android/build/generated/autolinking/autolinking.json"
NG=0
if [ ! -f "$F" ]; then
echo "OK: $F does not exist. The builder will regenerate it"
exit 0
fi
echo "Found: $F"
# 1) Is it tracked by Git?
if git ls-files --error-unmatch "$F" >/dev/null 2>&1; then
echo "NG: tracked by Git (remove it with git rm --cached)"; NG=1
fi
# 2) Is it ignored? Check the root, not only android/.gitignore
if git check-ignore -q "$F"; then
echo "OK: ignored by Git"
else
echo "NG: not ignored (add android/build/ to the root .gitignore)"; NG=1
fi
# 3) Any Windows-style absolute paths (C:\ or C:/)?
if grep -qE '"[A-Za-z]:(\\\\|/)' "$F"; then
echo "NG: Windows-style paths found. They do not exist on a Linux builder"; NG=1
else
echo "OK: no Windows-style paths"
fi
# 4) If .easignore exists, does it list android/build too?
if [ -f .easignore ] && ! grep -qE '^/?android/build/?$' .easignore; then
echo "Note: .easignore does not list android/build/"
fi
exit "$NG"I ran this against two sample files, one with Windows-style paths and one without. The first printed NG and exited with 1; the second printed OK and exited with 0. What I have not confirmed is whether the symptom actually disappears on a real EAS build.
Step 2 says to check the root rather than only android/.gitignore for a reason. One reporter wrote that they had to add android/build/ to the root .gitignore. Having the line only in the subfolder was not enough in their setup.
If it persists, make the builder discard it
The remedy one reporter actually used was a few lines at the top of android/settings.gradle that delete the cache every time. On anything other than Windows, which includes the Linux builder, the autolinking cache is always regenerated.
// Regenerate the autolinking cache on the builder every time.
// Even if a cache with Windows paths reaches EAS's Linux builder,
// it is thrown away here.
def autolinkingCacheDir = new File(rootDir, "build/generated/autolinking")
if (!System.getProperty("os.name").toLowerCase().contains("windows")) {
autolinkingCacheDir.deleteDir()
}The instruction was to place it before the pluginManagement block.
This silences the symptom, though, rather than removing the root: the stale cache still gets uploaded. If you regenerate android/ with expo prebuild each time, not committing android/ at all is the cleaner answer. And if you use .easignore, remember that .gitignore is no longer read. I wrote about that in .easignore を置くと .gitignore は読まれなくなります — EAS Build に上がるファイルを手元で数える (Japanese).
The order I would try
Smallest step to biggest:
- Run
npx expo-doctorand fix what it flags. - Inspect
android/buildwith the script above. - Rebuild with
eas build --platform android --clear-cache. - If it still fails, add the cache-discarding lines to
settings.gradle. - If it still fails after that, suspect the
imageineas.json, which is the territory of eas.json に image を書かないままだと、SDK を上げた日に Xcode も pnpm も一緒に動きます (Japanese).
"It passes on my machine" is not evidence that it passes on the builder. Counting what changes between the two before sending is a line I intend to keep holding.
Tonight, one small thing is worth doing on your own project: run git ls-files android/build | head. If nothing prints, you are past the first gate.