An OTA update crashes the store build on launch with a missing native view. The patch that added the view shipped normally, but the Android runtime version never moved, so the update was published as compatible with a binary that predates the native code. iOS behaves. Reproduced, fixed and gated in pingdotgg/t3code#20384.
Why it happens
@expo/fingerprint collects Android autolinking sources in getExpoAutolinkingAndroidSourcesAsync. A module can declare a gradle plugin resolved from Maven by id and version, with no local sourceDir - expo-widgets ships one. The collector passes plugin.sourceDir to path.relative(), which throws on undefined, and the surrounding catch returns []. One sourceless plugin therefore drops every Expo Android module from the hash, not just itself. In the reported app the fingerprint covered 0 expoAutolinkingAndroid sources where the fixed build covers 56.
From there the failure is silent. Any native edit to any Expo module stops changing the Android fingerprint, which is exactly the wrong signal under a fingerprint-based OTA policy - the runtime looks unchanged, so the update is routed to binaries that predate the native code. The bug is in @expo/fingerprint 0.21.5 and current canaries.
The fix
Skip gradle plugins without a sourceDir when collecting hash sources. They have no local files to hash, and their id and version are still fingerprinted:
if (typeof plugin.sourceDir !== 'string') {
continue
}
Carry it as a patch until it lands in a released @expo/fingerprint - the same skip was proposed upstream in expo/expo#51710.
The gate matters more than the patch. Refuse to publish an OTA from a fingerprint that hashes no Expo native modules. The sources carry a reason tag you can count:
expo_modules="$(eas fingerprint:generate --platform android --environment production --json \
| sed -n '/^{/,$p' \
| jq '[.sources[] | select((.reasons // []) | index("expoAutolinkingAndroid"))] | length')"
[ "${expo_modules:-0}" -eq 0 ] && exit 1
The general shape is worth keeping. A hashing pass that catches and continues can quietly cover nothing, and every "nothing changed" result downstream of it is then a lie. Verify what a fingerprint actually covered before trusting it to route updates.