◉RORK LABJP
●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Beta since 09/15 with no stable date yet; running projects in Expo Go has required login since 09/03●WIDGET — A write-up traces an iOS widget that renders black only in production builds●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Beta since 09/15 with no stable date yet; running projects in Expo Go has required login since 09/03●WIDGET — A write-up traces an iOS widget that renders black only in production builds●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…
Articles/App Dev
◇ App Dev/2026-10-15Intermediate

When Only EAS Build Fails with "No matching variant", Check Your Local android/build First

Gradle passes on your machine, but the Android build on EAS stops at "No matching variant of project". Reading the reports on GitHub, I traced how a Windows-generated autolinking cache can reach the Linux builder, and wrote a small script that checks for it before you queue a build.

EAS Build22Android55GradleExpo218Troubleshooting43

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 remedyLikely cause it addressesEffort
Fix what expo-doctor flagsConfig or dependency mismatchSmall
Align dependencies with npm dedupeMixed versions of the same packageSmall
eas build --clear-cacheA stale cache being restoredSmall (the build gets longer)
Stop the autolinking cache from being usedA file containing another OS's pathsMedium

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:

  1. Run npx expo-doctor and fix what it flags.
  2. Inspect android/build with the script above.
  3. Rebuild with eas build --platform android --clear-cache.
  4. If it still fails, add the cache-discarding lines to settings.gradle.
  5. If it still fails after that, suspect the image in eas.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.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

◇ App Dev2026-10-14
App Review Says "Guideline 2.1, Blank Screen": Three Places to Look in an Expo Release Build
It opens fine in TestFlight, yet review rejects it for a blank screen. Here is the order I check things in an Expo + EAS release build: which environment holds your variables, secret visibility during config resolution, and what the reviewer's device does differently.
◇ App Dev2026-10-09
When only notifications fail in Expo Go: three checks, then the shortest move to a development build
If push notifications never reach your Android device in Expo Go, check these three things before touching device settings, then move to a development build with the fewest steps. Also: how to spot what else Expo Go can't test.
◇ App Dev2026-09-23
It Crashed, but the Reviews Say "It Freezes" — When an Android Crash Leaves a Blank Screen Behind
Your crash reports show exceptions, yet users only ever say the app freezes. Here is why expo-updates plus the New Architecture can leave an empty Android screen behind, and the order I now use: a way out first, a bad-exit marker second, root cause last.