invertase / invertase/react-native-google-mobile-ads
[🐛] All ad loads (test IDs) fail with googleMobileAds/timeout ~10s after init on physical iPhone (iOS 26.6, TestFlight, new arch) — app-level fetch to ad hosts succeeds in the same session
- Dominant language
- TypeScript
- Stars
- 1k
- Forks
- 229
- Avg merge
- 3d 5h
- Merged PRs (30d)
- 7
Description
## What happened?
Every ad load (interstitial **and** rewarded, Google's official test ad unit IDs) fails with `googleMobileAds/timeout` — "The Google ad request was unable to be fulfilled before a timeout occurred" — **consistently ~10 seconds after `initialize()` resolves**, on a physical iPhone (iOS 26.6) running a TestFlight build. Both ad types fail **in the same second, every time**, across app restarts, days, and networks.
We instrumented the app with telemetry, so we can rule out most of the usual suspects with data rather than guesses:
**What works (same app process, same device, same session):**
- `MobileAds().initialize()` resolves OK (adapter statuses returned).
- UMP flow OK (`NOT_REQUIRED` region, `canRequestAds: true`).
- A plain `fetch()` from the RN JS side (NSURLSession) reaches all ad-serving hosts in well under 1s, verified in the **same sessions where the SDK times out seconds later**:
- `googleads.g.doubleclick.net` → 200 in 150–851 ms
- `pagead2.googlesyndication.com` → 404 in 115–499 ms
- `tpc.googlesyndication.com` → 404 in 115–516 ms
- `csi.gstatic.com` → 404 in 566–982 ms
- Our own analytics endpoint (Cloudflare Workers) receives POSTs from the app without issues.
**What fails:**
- `InterstitialAd.createForAdRequest(TestIds…).load()` → `googleMobileAds/timeout` after ~10s
- `RewardedAd.createForAdRequest(TestIds…).load()` → same, same second
- Automatic retries a minute later → same
- Every session, both ad formats always fail as a pair with identical timing.
**Ruled out by experiment (each on a separate TestFlight build):**
1. Network / DNS / ad-blockers: Wi-Fi and cellular both fail identically; no VPN, no DNS profile, no blocker apps, iCloud Private Relay off; Safari on the device reaches all the hosts above instantly.
2. Google *sample* app ID vs the app's real AdMob app ID in `GADApplicationIdentifier`: both time out (init OK with both).
3. `requestNonPersonalizedAdsOnly: true`: removing it makes no difference (tested via a one-shot throwaway instance after the main ones failed).
4. SDK/library staleness: library 16.5.0 (latest), pinned Google-Mobile-Ads-SDK 13.5.0 (latest with no timeout-related fixes in 13.6–13.8 release notes).
Since the app's own URLSession reaches the ad hosts in the very sessions where every SDK request dies at a fixed ~10s, the failure appears to be **inside the SDK's own request path** (its internal machinery, not the network).
Remaining variable we have not yet isolated: `newArchEnabled: true` (New Architecture). Possibly related: #821 (ad failures on iPadOS 26 with new arch). We can run a `newArchEnabled: false` build if that would help, and we're happy to run any other diagnostics — the app has a telemetry pipeline, so results come back as structured data.
### Code (condensed but faithful)
```ts
// startup (module init):
interstitial = InterstitialAd.createForAdRequest(TestIds…, { requestNonPersonalizedAdsOnly: true });
rewarded = RewardedAd.createForAdRequest(TestIds…, { requestNonPersonalizedAdsOnly: true });
// listeners attached for LOADED / ERROR / CLOSED
// after UMP (AdsConsent.requestInfoUpdate → loadAndShowConsentFormIfRequired → canRequestAds true):
await mobileAds().initialize(); // resolves OK
interstitial.load(); // -> ERROR googleMobileAds/timeout after ~10s
rewarded.load(); // -> ERROR googleMobileAds/timeout, same second
```
### Telemetry from one session (physical iPhone, iOS 26.6, TestFlight)
```
01:05:34 ad_state {os:"26.6", sdkAvailable:1, canRequestAds:1, umpReachable:1}
01:05:34 probe pagead2.googlesyndication.com 404 in 115ms (app-level fetch)
01:05:34 probe tpc.googlesyndication.com 404 in 115ms
01:05:35 probe googleads.g.doubleclick.net 200 in 150ms
01:05:35 ad_init {ok:1} (initialize resolved)
01:05:35 probe csi.gstatic.com 404 in 566ms
01:05:45 ad_load_fail {kind:"rewarded", code:"googleMobileAds/timeout"}
01:05:45 ad_load_fail {kind:"interstitial", code:"googleMobileAds/timeout"}
01:05:55 ad_exp {v:"no_npa", ok:0, code:"googleMobileAds/timeout"} (throwaway instance without requestNonPersonalizedAdsOnly)
```
## Platforms
iOS, but have not tested behavior on Android
## Environment
- react-native-google-mobile-ads: **16.5.0** (Google-Mobile-Ads-SDK 13.5.0, GoogleUserMessagingPlatform 3.1.0 — the versions pinned by the library)
- react-native: **0.81.5**, react: 19.1.0
- Expo SDK **54** (`expo@54.0.37`), built with **EAS Build** (production profile), distributed via **TestFlight**
- `newArchEnabled: true`, Hermes, TypeScript
- iOS deployment target 16.4; device: iPhone, **iOS 26.6** (physical device; Expo Go not involved — ads code is a no-op there by design)
- App is portrait-only, UI is a WKWebView (react-native-webview 13.15.0) — mentioning in case an app-level WKWebView interacts with the SDK's internal webview machinery
- Info.plist: `NSAppTransportSecurity` set to Google's recommended pair (`NSAllowsArbitraryLoadsForMedia`, `NSAllowsArbitraryLoadsInWebContent`), 50 SKAdNetwork IDs, `GADApplicationIdentifier` tested with both the Google sample app ID and our real one
- No ATT prompt (non-personalized only); UMP status NOT_REQUIRED (Japan)
Contributor guide
Research direction
Start with the condensed startup flow around mobileAds().initialize() and the interstitial.load() and rewarded.load() calls, then compare physical iOS builds with newArchEnabled enabled and disabled. Use the existing telemetry to verify whether both ad formats still fail at the same timeout; done means isolating whether New Architecture changes the result or ruling it out with comparable diagnostics.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- ios, react-native, typescript
- Domain
- mobile-dev
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100