◉CLAUDE LABJP
●2.1.301 — Adds the vim % motion, LaTeX math rendering in the VS Code panel, and a retry-watchdog limit for usage-limit waits (10/15)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 45 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…●2.1.301 — Adds the vim % motion, LaTeX math rendering in the VS Code panel, and a retry-watchdog limit for usage-limit waits (10/15)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 45 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…
Articles/Claude Code
⟐ Claude Code/2026-07-06Advanced

I Was Starting the Ad SDK Before the Consent Dialog — Fixing ATT and AdMob Ordering with Claude Code

Starting the ad SDK at launch initializes it before ATT consent, so the IDFA is all zeros and only your measurement goes thin while fill rate stays fine. Here is how I traced the scattered init paths with Claude Code and reshaped them to start ads only after ATT resolves.


✦ Premium Article

While staging a release, I noticed something odd in the AdMob dashboard. Fill rate and eCPM looked completely normal, yet the share of impressions that came through with SKAdNetwork attribution was clearly thinner than before. The ads were serving; only the measurement of those ads was missing.

I ruled out one suspect after another, and the last one standing was initialization order. In the wallpaper app I run as an indie developer, the ad SDK was starting at launch. At that moment the App Tracking Transparency (ATT) dialog had not been shown yet, so the SDK came up with an empty IDFA. I was assembling the measurement foundation before asking for consent.

Let me start from what actually goes missing, then walk through having Claude Code list every init path, folding them into a single "start after ATT" flow, and guarding the order so it can never drift again — all with the real code.

What Goes Missing When You Start MobileAds Before the Dialog

On iOS, an app needs ATT permission to reach the advertising identifier (IDFA). Until the user taps "Allow," the IDFA returned by ASIdentifierManager is an all-zeros value.

The ad SDK reads the available identifiers and measurement settings at init time. If the IDFA is still empty here, SKAdNetwork attribution and the init payloads handed to each mediation partner get assembled on top of a pre-consent state. Serving itself does not need the IDFA, so fill rate does not drop. Only measurement suffers — which is exactly why staring at revenue numbers won't reveal it.

Init orderAd servingIDFAMeasurement (SKAdNetwork, etc.)
Ad SDK starts before ATTWorksEmpty at initGoes thin
Ad SDK starts after ATT resolvesWorksPresent if allowedLines up

Serving is unchanged, but measurement drifts. That quiet drift was the nastiest part of this ordering bug.

The Broken Order (Before)

The offending code started the ad SDK during SwiftUI app launch. The ATT request was fired later, almost as an afterthought, on some other screen after ads had already appeared a few times.

import GoogleMobileAds
 
@main
struct WallpaperApp: App {
    init() {
        // ❌ Starts the ad SDK at launch.
        // ATT consent isn't resolved yet, so the SDK inits with an empty IDFA.
        MobileAds.shared.start()
    }
 
    var body: some Scene {
        WindowGroup { RootView() }
    }
    // ATT was requested later on another screen (= too late)
}

init() runs at the very earliest stage of launch. The ATT dialog will not even display until the app becomes active, so this order makes it structurally impossible to avoid finalizing the measurement base before consent.

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦You can tell when thin SKAdNetwork measurement — despite healthy fill rate — comes from starting the ad SDK before ATT, and stop guessing at the wrong cause
✦You can move a broken 'start ads at launch' path to a 'start after ATT resolves' shape, with before/after Swift you can paste into your own app
✦You can collapse every ad-start entry point into one and guard it with CI and an assertion so the ordering can never silently regress
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Claude 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 →

Related Articles

⟐ Claude Code2026-10-15
The Hook That Was Meant to Guard My Japanese Folder Never Blocked Anything — Python Was Reading stdin as cp932
A PreToolUse hook guarding folders like 経理 or 議事録 silently lets edits through when Python's stdin defaults to cp932. How I reproduced it, the three-line fix, and one command to check your own hooks.
⟐ Claude Code2026-10-14
I Set onFailure to block, and the Docs' rm -rf Guard Still Let Everything Through Without jq
When a PreToolUse guard hook fails, does the action go ahead or stop? The docs' own rm -rf example exits 0 and waves everything through when jq is missing. Ten failure modes run for real, the fix, and a script that finds fail-open guards in settings.json.
⟐ Claude Code2026-10-14
Grep's First 250 Lines Touched Only 42 of the 183 Files That Matched
Searching an article repository for the word hook returned 1,312 lines across 183 files. ripgrep, git grep and grep all finished in 11 to 35 ms, so speed was never the issue. Output size and ordering were. A script that estimates what a 250-line cutoff will miss, plus a search order backed by measurements.