Files
obsidian-vault/work/projects/cpm-web-extension-data-analysis-summary-2026-10-08.md
T

8.7 KiB
Raw Blame History

title, date, status, tags
title date status tags
CPM Web Extension failures — data analysis summary 2026-10-08 draft-for-asana
work
cpm
webkit

CPM Web Extension failures — data analysis summary

Companion note: cpm-web-extension-repro-and-fixes-summary-2026-10-08 (tests and fixes). Detailed working notes (Russian): cpm-web-extension-repro-and-fix-2026-10-08, cpm-web-extension-breakage-session-summary-2026-10-07.

TL;DR

  • Most CPM "failures" are not a dead extension. The failure pixels mean that within 4 s after a page finished loading, the app received no refreshCpmDashboardState call from the extension. A late answer counts as a failure too.
  • The largest cohort, a failure while the app-side state looks healthy, mostly happens right after WebKit wakes the extension's background after idle. A test reproduces it exactly when the background service worker takes longer than 4 s to start. Many of these failures are late responses, not lost ones.
  • The second-largest cohort on iOS is WebKit error 6, where the background fails to load. Its root cause is in WebKit: the 30 s idle-unload timer starts before the background has finished loading.
  • A Network process crash explains only 20–45% of the "healthy" stuck cases. It is the only mechanism that breaks every tab at once, including pages that were already open.
  • New telemetry (phase 1) separates "slow start" from "lost message" and "late" from "lost". It is ready and should ship before or together with the fixes.

What the pixels measure

  • initialization_failed_after_<reason>: the first navigation with no CPM answer within 4 s. Sent once per day per reason.
  • messaging_stuck_<reason>: a second failure in the same episode, from a navigation that started after the first failure. This triggers recovery M4, a reload of the extension.
  • recovered_without_extension_reload and recovered_after_extension_reload: the episode closed because an answer finally arrived.
  • Each failure pixel carries a snapshot of app state: whether the context is loaded, background view and process state, tab binding, context errors, and bg_events, a short timeline of background lifecycle events.

Scope

  • One UTC day, October 5, 2026. Only events after the release tag of iOS 7.240 and macOS 1.210.
  • First initialization_failed, daily: 6,624 on iOS, 1,377 on macOS.
  • messaging_stuck, daily: 1,440 on iOS, 487 on macOS.
  • Daily pixels count installs per day, not users or episodes.

Cohorts of stuck reports

These are snapshot states at the time of the timeout, not root causes. Each report is assigned to one cohort, by priority.

  1. Healthy snapshot, no visible error. iOS 1,410, macOS 705 (stuck counts); about 55% of iOS stuck daily.
    • The Network process had changed since the context loaded in 17–21% of iOS and 30–45% of macOS reports.
    • The rest is called "group 1" below: iOS 748, macOS 228 (daily).
  2. WebKit error 6 (BackgroundContentFailedToLoad). iOS 579, about a third of iOS stuck reports; macOS 3.
  3. No background view. iOS 158, macOS 35.
  4. Tab not attached to the extension. Wrong controller or missing content scripts. iOS 81, macOS 102. On macOS these appear only after the 1.210 release tag.
  5. Background process died (died_*). iOS 14; 86% of these also had a Network process change, which looks like iOS killing both processes at once.

Group 1: the largest unexplained cohort

  • About half follow a background wake. The timeline tail is "old view deallocated, new view created", with the new view under 30 s old: iOS 373 of 748, macOS 120 of 228.
  • About a quarter on iOS and 15% on macOS follow a context load or reload: load → view or base_url_changed → view.
  • On macOS, 63% of reports were sent shortly after an app update, against 52% across all stuck reports.
  • Memory pressure and context errors are almost always absent.
  • Both macOS 26.7 and 27.0 are affected. The iOS OS-version split has not been queried yet.

Recoveries:

  • An initialization_failed episode recovers without a reload on 23% of iOS and 25% of macOS device-days. Counting only "other" navigations, the figure is 33% on both platforms. Recoveries repeat on the same devices, about 1.9 per device-day on iOS.
  • After M4, a stuck episode recovers on 64% of iOS and 74% of macOS device-days.
  • Recovery pixels do not yet say whether the answer was late or came from a later page. Phase 1 telemetry adds this.

Init failures compared with stuck reports, iOS healthy snapshots:

  • After a context load: 32% of init failures, 18% of stuck reports.
  • After a wake: 44% of init failures, 61% of stuck reports.
  • So failures after a context load usually clear on their own, while failures on wake more often escalate to stuck.

Theories and status

  • Network process crash leaves a dead service worker. Confirmed by test. Explains at most 20–45% of healthy stuck reports. Fixed in the app; fix not merged.
  • First-load message drop, WebKit 317981-like. Confirmed by test. At launch the existing gate already covers it; mid-session install or upgrade was a gap, now fixed in the app (not merged).
  • Service worker dies while the Network process stays alive. Confirmed by test, but the app cannot detect it. Mitigation: restart the background after the first failure.
  • Error 6. Root cause found in WebKit source and reproduced on macOS. Not reproduced on an iPhone with iOS 26.7.1. Telemetry added.
  • Slow service-worker start on wake. A start longer than 4 s reproduces the group 1 signature exactly. Why real devices are that slow is unknown; candidates are CPU contention with the loading tab, process launch, the extension process's priority, and a busy Network process. Telemetry added.
  • Context-load cleanup when the base URL changes (WebKit 312231@main, bug 320812). Reproduced on macOS: with about 20–24k network-cache entries, content scripts are delayed by 0.75–1.35 s. Explains part of the "after load or reload" subset. Possible mitigation: give the extension a stable base URL.
  • Ruled out: device lock and data protection, a stale iOS tab URL, a macOS feature-flag flip explaining the tab-binding cohort, the low-memory notification, background freezes of 10–75 s as a cascade trigger, and killing the background WebContent process.
  • Not reproduced yet:
    • the macOS tab-binding cohort;
    • the "no background view" cohort;
    • error code 7;
    • Lockdown Mode;
    • the first probe after M4 failing (670 on iOS, 232 on macOS).

Relevant WebKit bugs

  • 317981: a message sent while the background is still loading is dropped. Reproduced on macOS 26.7.1 for the first load of a new extension identifier. Fixed in the safari-7625 branch.
  • 324743: a service worker is treated as loaded before its registration completes. Fixed September 22, not in 26.7 or 27.0. Fits the wake path; no deterministic repro.
  • 320812, introduced by 312231@main (26.6+): when the base URL changes, context load deletes all website data of the previous origin, including the whole disk cache, before it injects content or loads the background. The DDG extension gets a new base URL on every launch and every reload. Fixed by 318756@main and cherry-picked to safari-7625; which OS build ships it is unverified.
  • 314194 (open): Lockdown Mode blocks every service worker, so CPM cannot work. The share of affected users is unknown.
  • 322881 (clients lost after reload) and 324754 (reloaded worker without extension bindings under site isolation): low relevance to CPM, unproven.
  • 319976 (fixed): loadBackgroundContent completion called twice on error. We use this API.
  • To file:
    1. A dead worker after a Network process crash goes unnoticed by WebExtensionContext.
    2. The 30 s unload timer is started before the background finishes loading, which causes error 6.

Telemetry added (phase 1, not merged)

All values are buckets or enums, with no URLs or identifiers.

  • On failure pixels:
    • bg_ready_after: whether the background had finished loading at the time of the failure, and how long the load took;
    • bg_view_age: how long ago the current background view was created;
    • app_state;
    • open_tabs and loading_tabs;
    • bg_load_error_app_state and bg_load_error_after: app state and timing for error 6.
  • On recovery pixels:
    • recovery_document: whether the failed navigation answered late, a later navigation answered, or the response matched no single tab;
    • recovery_after: time from the episode's first failure to the recovery.

How to read the results:

  • bg_ready_after=pending with a young view means a slow wake.
  • bg_ready_after loaded with no answer means a lost message.
  • recovery_document=failed_navigation means a late answer, not a loss.
  • For error 6, bg_load_error_after=25s_to_35s points at WebKit's unload timer.