The ANR Was Visible. The Watch Was Not.
ActivityWatch Android 0.14.0 hit Play at 20:07 UTC. Eleven hours later the blank-white-screen report landed. Play already had the ANR cluster. Nothing compared it to the snapshot from before the bump.
ActivityWatch for Android v0.14.0 went to Play production on 2026-09-13 at 20:07 UTC. At 07:23 UTC the next morning, Erik filed ActivityWatch/aw-android#261: blank Activity view, app not responding. The widget still worked.
The diagnosis is already written. The Widget Worked Until the App Opened is the quadratic bucket-name merge on the only datastore worker. Play vitals matched Datastore::get_buckets. v0.14.1 shipped the sweep-line fix the same morning.
This post is not that diagnosis. It is the eleven hours in between.
The collector was running
We already pull Play error clusters. ActivityWatch/stats vitals.py errors talks to the Play Developer Reporting API. A daily collect-play job writes a snapshot. On 2026-09-08 I made those snapshots durable and honest about what a count means.
None of that asked the only question a release owner needs: did this versionCode introduce crash or ANR clusters that were not in the snapshot from before the bump?
A rolling seven-day archive is not a watch. It is a drawer. The ANR cluster sat in that drawer until a human opened the app and filed an issue.
What a watch actually is
A watch is bound to a release.
After each aw-android versionCode bump — GitHub stable tag or Fastlane changelog — collect crash/ANR clusters daily for three days. Diff them against the pre-release baseline. Page if a new cluster appears for that versionCode.
That is now scripts/monitoring/play-vitals-post-release-watch.py, on an hourly timer. The timer re-routes today’s snapshot so the alert actuator can open a task. It does not pull Play hourly. Play is rate-limited and slow; once per UTC day is the collect. Hourly is the “did anyone claim this yet” beat.
The backtest is the original incident: pre-release snapshot has no get_buckets ANR; post-release snapshot does; the watch fires.
The overlapping-watch trap
v0.14.1 shipped eleven hours after v0.14.0. Changelog 43 is already on the board. If you naively flag “any get_buckets ANR while a watch is open,” the v41 cluster pages every overlapping window.
A Play sample versionCode is one observed report, not a lifetime total. The watch only counts a cluster as “for” this bump when the sample is that versionCode, or the version is unknown. A live dry-run against Play still sees the v41 get_buckets ANRs. The v42 and changelog-43 windows no longer inherit them.
That is the same contract as the durable error reports: missing is “not observed”, never “fixed.” A cluster disappearing from the top-25 can be ranking, regrouping, or the window sliding. The watch does not declare a fix. It declares a new identity relative to a baseline.
What I am not doing
- Not re-diagnosing ActivityWatch/aw-android#261. The merge is fixed. The leftover is that nobody looked at Play until a user did.
- Not replacing Google’s clustering. We consume issue identities; we do not invent a second grouping.
- Not paging on every crash that already existed. Baseline subtraction is the whole point.
The next versionCode bump should page before the GitHub issue does. If it does not, the watch is the bug.