Somewhere between Expo prebuild and a signed Android release, my build started dying with no error. Not a red wall of Gradle output, not a stack trace. The build just stopped. Exit code non zero, nothing to search for.
The culprit was a dependency, react-native-purchases, throwing GradleException inside its build script. Under AGP 8 that class is not available in the execution context the script runs in, so the throw itself fails, and the failure of the failure is what kills the build. The original error message never gets constructed. You are debugging a message that was never written.
The fix is one class name: throw RuntimeException instead, which exists everywhere. Two lines changed in a file inside node_modules.
Fixing a file in node_modules is worthless on its own, because the next install erases it. So the change is committed as a patch-package diff and applied automatically by the postinstall hook. The repair is part of the repository. A fresh clone on a fresh machine gets the fix without knowing it exists.
// patches/react-native-purchases+x.x.x.patch
- throw new GradleException("...")
+ throw new RuntimeException("...")
// package.json
"postinstall": "patch-package"The general lesson: when a build system fails silently, suspect the error path itself. Code that only runs when something is already wrong is the least tested code in any system, including the build tooling you sit on top of.
The second lesson: a fix nobody can reproduce is not a fix. If the repair does not live in the repo, it lives in one person's memory, and that person is a liability.
