fix(mobile): pin play-services-ads to 25.0.0 so the release build compiles - #344
Conversation
…piles The Android Release (AAB) workflow failed at :react-native-google-mobile-ads:compileReleaseKotlin because play-services-ads resolved to 25.4.0, whose jars carry Kotlin 2.3.0 metadata that the Expo SDK 55 (Kotlin 2.1) toolchain cannot read: Module was compiled with an incompatible version of Kotlin. The binary version of its metadata is 2.3.0, expected version is 2.1.0. react-native-google-mobile-ads 16.3.3 only asks for play-services-ads 25.0.0; 25.4.0 was a transitive upgrade Gradle picked on a fresh install (a stale local Gradle cache is why the local APK still built). Force the play-services-ads SDK artifacts back to 25.0.0 — the Kotlin-2.1-safe version RNGMA targets — via a resolutionStrategy rule in the existing release-build-fixes config plugin, so both the local APK and the CI AAB builds stay on it. play-services-ads-identifier is excluded so it keeps its own (18.x) line. Verified via `gradlew :react-native-google-mobile-ads:dependencyInsight --configuration releaseCompileClasspath --dependency play-services-ads`: play-services-ads and play-services-ads-api both resolve to 25.0.0 by rule, play-services-ads-identifier stays 18.0.0, BUILD SUCCESSFUL. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
There was a problem hiding this comment.
Code Review: PR #344
Scope: PR #344 — fix(mobile): pin play-services-ads to 25.0.0 so the release build compiles
Recommendation: APPROVE
Summary
PR #344 adds a Gradle resolutionStrategy rule to the existing with-android-release-build-fixes config plugin, pinning com.google.android.gms:play-services-ads* to 25.0.0. The fix correctly targets the root cause (Gradle's transitive resolution silently upgrading to 25.4.0, which ships Kotlin 2.3.0 metadata incompatible with Expo SDK 55 / Kotlin 2.1.0). The implementation is idiomatic, idempotent, and follows the established pattern in the file. No Critical or High findings survive.
Findings
Critical
None
High
None
Medium
None
Low / Info
None
Subagents
| Agent | Verdict |
|---|---|
| parity-checker | PAIRED — Android-only build tooling; no web equivalent exists or is expected |
| i18n-syncer | N/A — no user-facing strings changed |
| contract-aligner | N/A — no packages/shared types or orbit-api changes |
| security-reviewer | N/A — no orbit-api code changed |
Validation
| Check | Result |
|---|---|
| Lint | N/A — permission not available in this environment |
| Type check | N/A — permission not available in this environment |
| Tests | N/A — permission not available in this environment |
| Build (api) | N/A — orbit-api not touched |
Deferred — N/A dimensions & files not verdicted
- #8 DESIGN.md/AI-slop — gated to
apps/*UI files; this is a build plugin, not a UI file. - #10 i18n — no user-facing strings added or removed.
- #11 Contract drift + backward-compat — no
packages/sharedZod schemas or orbit-api DTOs changed. - #12 Security (API side) — orbit-api not touched; frontend security categories reviewed inline (no injection risk, dependency pin is a security improvement).
- #13 Backend hard rules — gated to orbit-api; not touched.
- Validation — lint, typecheck, and test runs require shell permissions not available in this environment.
What's good
- The idempotency guard (
contents.includes(ADS_VERSION_PIN_MARKER)) prevents double-injection acrossexpo prebuildruns — matches the existing pattern forROOT_STAGING_MARKER. - The
play-services-ads-identifierexclusion is correct: that artifact follows the independent 18.x version line and must not be forced to 25.0.0. - The Groovy
.becauseclause in the resolution strategy is excellent: it explains exactly why the pin exists (Kotlin 2.3 metadata) so future maintainers know when it is safe to remove it. - The fix is scoped entirely to the Android build tooling layer — zero runtime behavior change, no web parity required.
- The PR body includes a working
dependencyInsightcommand to verify the pin, making the fix independently reproducible without a full AAB build.
Recommendation
Approve and merge. Re-dispatch the Android Release (AAB) workflow after merge to produce the signed AAB.
🤖 Generated with Claude Code
There was a problem hiding this comment.
PR Review — #344
Recommendation: APPROVE
| Severity | Count |
|---|---|
| Critical (incl. old-client breaks) | 0 |
| High | 0 |
| Medium | 0 |
| Low / Info | 0 |
Scope: Single file change — apps/mobile/plugins/with-android-release-build-fixes.js (+32/-0)
The new withProjectBuildGradle block pins play-services-ads to 25.0.0 in the Android release build. Implementation is idiomatic Expo config plugin, idempotent (marker-guarded so it won't double-insert), and correctly excludes play-services-ads-identifier from the pin. Android-only build tooling — no web parity required. All other rubric dimensions are N/A for this change.
Clean diff, no issues found.
|
…1) (#345) #344 pinned play-services-ads to 25.0.0, which compiles under Kotlin 2.1 but predates the AgeRestrictedTreatment API that react-native-google-mobile-ads 16.3.3 references, so :react-native-google-mobile-ads:compileReleaseKotlin then failed with "Unresolved reference 'AgeRestrictedTreatment'". The catch: that API and the Kotlin 2.3 metadata bump both landed in 25.3.0, so no single ads release has the API under Kotlin 2.1 — except 25.2.0, the release right before the bump. It carries the API and is still Kotlin 2.1. Verified by compiling the exact failing task locally against each candidate: - 25.0.0 -> Unresolved reference 'AgeRestrictedTreatment' - 25.3.0 -> Kotlin metadata 2.3.0 vs expected 2.1.0 - 25.2.0 -> BUILD SUCCESSFUL Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>



Problem
The Android Release (AAB) workflow (failed run) died at
:react-native-google-mobile-ads:compileReleaseKotlin:Root cause
play-services-adsresolved to 25.4.0 (released 2026-06-17), whose jars carry Kotlin 2.3.0 metadata. Expo SDK 55 / RN 0.83 compiles with Kotlin 2.1.0, which can't read 2.3.0 metadata.react-native-google-mobile-ads@16.3.3only asks forplay-services-ads:25.0.0(sdkVersions.android.googleMobileAds). The25.4.0was a transitive upgrade Gradle picked on a fresh install — which is also why the local APK still built (its Gradle cache still had the pre-25.4.0 tree). The AdMob release notes still claim "min Kotlin 2.1.0," but the 25.4.0 artifact's metadata is demonstrably 2.3.0.Fix
Add a
resolutionStrategyrule to the existingwith-android-release-build-fixesconfig plugin that forcescom.google.android.gms:play-services-ads*back to 25.0.0 — the Kotlin-2.1-safe version RNGMA targets.play-services-ads-identifieris excluded so it keeps its own (18.x) version line. Because both the local APK and the CI AAB runexpo prebuild, both pick up the pin.Verification (local, no full build needed)
gradlew :react-native-google-mobile-ads:dependencyInsight --configuration releaseCompileClasspath --dependency play-services-ads:Parity
Android-build tooling only — no web/runtime equivalent, no app behavior change. After merge, re-dispatch the Android Release workflow to produce the signed AAB.
🤖 Generated with Claude Code