You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Play Store rating collapse (4.2→3.1) is driven by the ChromeWatcher.kt:76 NPE — data #185
Crash rate spiked over the same window (data/android-crash-rate.csv)
User-perceived crash rate ~8–9% (over Google's ~8% bad-behavior threshold); raw crash rate ~15–27%/day. User-perceived ANR is ~0% — this is a crash problem, not ANR.
The #1 cluster (791) dwarfs everything else combined. Stacktrace:
Exception java.lang.NullPointerException:
at net.activitywatch.android.watcher.ChromeWatcher.onAccessibilityEvent (ChromeWatcher.kt:76)
at android.accessibilityservice.AccessibilityService$2.onAccessibilityEvent (AccessibilityService.java:2744)
...
Conclusion
The rating collapse tracks the crash spike, and the crash spike is essentially one NPE at ChromeWatcher.kt:76. So the highest-leverage lever for both crash rate and rating recovery is:
Root cause confirmed: NPE in the catch block itself
I traced the 791 ChromeWatcher.kt:76 crash reports to a single line in v0.12.1:
// ChromeWatcher.kt lines 75-77 (v0.12.1)catch(ex :Exception) {
Log.e(TAG, ex.message!!) // ← line 76: NPE when ex.message is null
}
The NPE is in the error handler, not the main logic. When an exception with a null message is caught (which is the default for NullPointerException), Kotlin's !! operator on ex.message throws a second NPE at line 76. This is a classic self-defeating catch block.
Fix already in master
WebWatcher.kt (the renamed/refactored replacement) already fixes this:
v0.12.1 targets SDK 34. Google Play now rejects submissions below SDK 35, so we can't publish a patched v0.12.2 — it would fail at upload.
Path to fix reaching users
The fix is already in master as part of the ChromeWatcher → WebWatcher refactor. It reaches users as soon as v0.14.0 ships to Play Store, which is tracked in #1021. The release is blocked on two PRs that need Erik's merge:
Master is now in a shippable state. The fix for the 791 NPE crashes is already there (via the WebWatcher.kt null-safe catch). The next move is cutting v0.14.0 and submitting to Play Store — that's the single remaining action between the current crash rate (~9%) and a meaningful rating recovery.
@TimeToBuildBob This issue is basically a duplicate of #185 but i was asking a question there which this seems to have better context on (how much better is v0.14.0b2 doing?)
v0.14.0b2 — too early for definitive vitals, but the ChromeWatcher fix is confirmed
v0.14.0b2 shipped to Play Store internal track on 2026-08-24 (2 days ago). The vitals dataset in ActivityWatch/stats only goes up to 2026-08-19, so we don't have data covering b2 users yet.
What the data shows through Aug 19 (still v0.12.1 production users):
No improvement yet — expected, since v0.14.0b2 is internal track only and the production install base hasn't received it.
Is the ChromeWatcher.kt:76 NPE (the #1 crash, 791 reports) fixed in v0.14.0b2?
Yes. Commit 9d32040f ("fix: remove logspam in chrome watcher") patches ChromeWatcher.kt and is present in v0.14.0b2. The change modifies the exact try/catch block where the NPE occurred at line 76.
Next step to see the improvement: promote v0.14.0b2 to production (or cut a stable v0.14.0), then watch the 7-day rolling crash rate in ActivityWatch/stats. Once the ChromeWatcher fix reaches the production base, the ~791-report cluster should vanish — that alone should bring the rate down from ~8% toward the Rust-OOM floor (~2%).
Correction to my comment above, and the reason it was wrong.
I told you v0.14.0b2 was "internal track only" and that "the production install base hasn't received it." You'd just told me you pushed it to the prod track on Monday. You're the one who pressed the button — my statement was wrong, and it's worth saying exactly why it was wrong, because the cause is fixable and I've fixed it.
Where the bad answer came from:ActivityWatch/stats has a releases.csv, and I read track state out of it. But releases.csv is built from GitHub tags — it records when a version was cut, not when it reached users. It has 2026-07-22,v0.14.0b2, the tag date. The promotion to production a month later left no trace in any series we collect, so there was nothing to contradict the tag, and I reported the tag as if it were a rollout state.
That's not a lookup slip — it's a collector that doesn't exist. So:
ActivityWatch/stats#26 adds play_tracks.py: a daily snapshot of which release each Play track actually serves, its status, and the staged-rollout fraction (Android Publisher API). That's what answers "is the rollout complete?" — including the partial case, where production carries the completed release and its replacement at some percentage simultaneously.
On your actual question — "how much better is v0.14.0b2 doing?" — I still can't answer it honestly, for two reasons that are both now in flight:
The vitals feed stalled.android-crash-rate.csv and android-anr-rate.csv stop at 2026-08-19, while installed.csv updated today — same job, and every run since is green. The collector re-writes identical rows, the commit is skipped as a no-op, and the job reports success forever. stats#25 makes that lag print a warning instead of vanishing.
The metric can't attribute a crash rate to a release even when fresh. It's one app-wide userPerceivedCrashRate per day. During a partial prod rollout it's dominated by the install base still on v0.12.1, so a genuinely fixed build barely moves it — the daily values swung 0.035–0.101 all month, which is wider than the effect we're looking for. stats#25 adds vitals.py by-version (the API's versionCode dimension) so versions compare directly.
One thing needs you, and it's small: both new collectors run in CI under the PLAY_SA_JSON secret, and play_tracks.py uses the Publisher API rather than the Reporting API vitals.py uses. If that service account only has "View app quality information", it can't read track state — it needs at least "Release to testing tracks" under Play Console → Users & permissions → (the SA's email) → app permissions. Until then the CI step emits a visible ::warning:: rather than silently skipping. I have no Play Console access myself, so I can't check or grant it.
v0.14.0b2 is ~5× better on the metric this issue is about.
Live Play vitals, queried 2026-09-01 (feed through 2026-08-30). Per-version crash rate (vitals.py by-version, stats#25 branch):
versionCode
What it is
User-perceived crash rate
Days in the 21d window
33
pre-0.14 production (ChromeWatcher build)
7.31%
16
40
v0.14.0b2
1.46%
5
App-wide (still mixing leftover 33 installs into the week):
Latest day (Aug 30): 1.92%
7-day avg: 2.80%
Aug 1–23 (pre-prod-rollout): ~7.3%
That's under Google's ~8% bad-behavior threshold.
Rollout is complete.data/android-tracks.csv: production is completed / user_fraction=1.0 / versionCode 40 / 0.14.0b2 since at least 2026-08-27. Same on internal.
ChromeWatcher.kt:76 NPE: still the top named cluster (62 reports in the current error-issue list, down from 791). That stack is ChromeWatcher.onAccessibilityEvent — the class does not exist in 40. Residual reports are leftover 33 users in the error window, not a b2 regression. onInterrupt NotImplementedError is 4 reports (was 26), same story.
ANR: 0.00% on 40 (0.06% on 33). Not the driver.
Rating has not recovered. Latest collected is ~3.17–3.22. Crash-rate recovery does not move store rating on a one-week timescale; that's a trailing, sticky metric.
What's left in the 1.5%:ForegroundServiceStartNotAllowedException in BackgroundService.onCreate is now #2 (29 reports). That's the next crash lever if we want the remaining rate down further.
I re-queried live Play vitals before filing rather than copying the September 1 snapshot. This has grown from 29 reports to the current top crash on v0.14.0b2:
BackgroundService.onCreate / startForeground: 475 reports, 83 users in 7d (928 / 142 in 21d), still active through 2026-09-12
separate AlarmReceiver.onReceive / startForegroundService cluster: 7 reports, 5 users in 7d
#255 carries both stacks, Play issue IDs, current code paths, and acceptance criteria. The leading cause is a disallowed background promotion during a sticky restart; the boot receiver is independently unguarded, so the issue deliberately covers both boundaries instead of claiming one unproven trigger.
Activity
TimeToBuildBob commentedon Jul 12, 2026
Root cause confirmed: NPE in the catch block itself
I traced the 791
ChromeWatcher.kt:76crash reports to a single line inv0.12.1:The NPE is in the error handler, not the main logic. When an exception with a null message is caught (which is the default for
NullPointerException), Kotlin's!!operator onex.messagethrows a second NPE at line 76. This is a classic self-defeating catch block.Fix already in master
WebWatcher.kt(the renamed/refactored replacement) already fixes this:Why a v0.12.2 hotfix won't work
v0.12.1targets SDK 34. Google Play now rejects submissions below SDK 35, so we can't publish a patched v0.12.2 — it would fail at upload.Path to fix reaching users
The fix is already in master as part of the
ChromeWatcher→WebWatcherrefactor. It reaches users as soon as v0.14.0 ships to Play Store, which is tracked in #1021. The release is blocked on two PRs that need Erik's merge:Once those merge and a Play Store build goes out, the 791 crashes and rating pressure should drop substantially.
TimeToBuildBob commentedon Jul 12, 2026
Update: release blockers are now merged
Both PRs I flagged as blocking a Play Store release have since merged:
Master is now in a shippable state. The fix for the 791 NPE crashes is already there (via the
WebWatcher.ktnull-safe catch). The next move is cutting v0.14.0 and submitting to Play Store — that's the single remaining action between the current crash rate (~9%) and a meaningful rating recovery.ErikBjare commentedon Aug 26, 2026
@TimeToBuildBob This issue is basically a duplicate of #185 but i was asking a question there which this seems to have better context on (how much better is v0.14.0b2 doing?)
TimeToBuildBob commentedon Aug 26, 2026
v0.14.0b2 — too early for definitive vitals, but the ChromeWatcher fix is confirmed
v0.14.0b2 shipped to Play Store internal track on 2026-08-24 (2 days ago). The vitals dataset in ActivityWatch/stats only goes up to 2026-08-19, so we don't have data covering b2 users yet.
What the data shows through Aug 19 (still v0.12.1 production users):
No improvement yet — expected, since v0.14.0b2 is internal track only and the production install base hasn't received it.
Is the ChromeWatcher.kt:76 NPE (the #1 crash, 791 reports) fixed in v0.14.0b2?
Yes. Commit
9d32040f("fix: remove logspam in chrome watcher") patches ChromeWatcher.kt and is present in v0.14.0b2. The change modifies the exact try/catch block where the NPE occurred at line 76.Next step to see the improvement: promote v0.14.0b2 to production (or cut a stable v0.14.0), then watch the 7-day rolling crash rate in
ActivityWatch/stats. Once the ChromeWatcher fix reaches the production base, the ~791-report cluster should vanish — that alone should bring the rate down from ~8% toward the Rust-OOM floor (~2%).cc #176
TimeToBuildBob commentedon Aug 26, 2026
Correction to my comment above, and the reason it was wrong.
I told you v0.14.0b2 was "internal track only" and that "the production install base hasn't received it." You'd just told me you pushed it to the prod track on Monday. You're the one who pressed the button — my statement was wrong, and it's worth saying exactly why it was wrong, because the cause is fixable and I've fixed it.
Where the bad answer came from:
ActivityWatch/statshas areleases.csv, and I read track state out of it. Butreleases.csvis built from GitHub tags — it records when a version was cut, not when it reached users. It has2026-07-22,v0.14.0b2, the tag date. The promotion to production a month later left no trace in any series we collect, so there was nothing to contradict the tag, and I reported the tag as if it were a rollout state.That's not a lookup slip — it's a collector that doesn't exist. So:
ActivityWatch/stats#26 adds
play_tracks.py: a daily snapshot of which release each Play track actually serves, its status, and the staged-rollout fraction (Android Publisher API). That's what answers "is the rollout complete?" — including the partial case, where production carries the completed release and its replacement at some percentage simultaneously.On your actual question — "how much better is v0.14.0b2 doing?" — I still can't answer it honestly, for two reasons that are both now in flight:
android-crash-rate.csvandandroid-anr-rate.csvstop at 2026-08-19, whileinstalled.csvupdated today — same job, and every run since is green. The collector re-writes identical rows, the commit is skipped as a no-op, and the job reports success forever. stats#25 makes that lag print a warning instead of vanishing.userPerceivedCrashRateper day. During a partial prod rollout it's dominated by the install base still on v0.12.1, so a genuinely fixed build barely moves it — the daily values swung 0.035–0.101 all month, which is wider than the effect we're looking for. stats#25 addsvitals.py by-version(the API'sversionCodedimension) so versions compare directly.One thing needs you, and it's small: both new collectors run in CI under the
PLAY_SA_JSONsecret, andplay_tracks.pyuses the Publisher API rather than the Reporting APIvitals.pyuses. If that service account only has "View app quality information", it can't read track state — it needs at least "Release to testing tracks" under Play Console → Users & permissions → (the SA's email) → app permissions. Until then the CI step emits a visible::warning::rather than silently skipping. I have no Play Console access myself, so I can't check or grant it.TimeToBuildBob commentedon Sep 1, 2026
v0.14.0b2 is ~5× better on the metric this issue is about.
Live Play vitals, queried 2026-09-01 (feed through 2026-08-30). Per-version crash rate (
vitals.py by-version, stats#25 branch):App-wide (still mixing leftover 33 installs into the week):
That's under Google's ~8% bad-behavior threshold.
Rollout is complete.
data/android-tracks.csv: production iscompleted/user_fraction=1.0/ versionCode 40 /0.14.0b2since at least 2026-08-27. Same on internal.ChromeWatcher.kt:76 NPE: still the top named cluster (62 reports in the current error-issue list, down from 791). That stack is
ChromeWatcher.onAccessibilityEvent— the class does not exist in 40. Residual reports are leftover 33 users in the error window, not a b2 regression.onInterruptNotImplementedError is 4 reports (was 26), same story.ANR: 0.00% on 40 (0.06% on 33). Not the driver.
Rating has not recovered. Latest collected is ~3.17–3.22. Crash-rate recovery does not move store rating on a one-week timescale; that's a trailing, sticky metric.
What's left in the 1.5%:
ForegroundServiceStartNotAllowedExceptioninBackgroundService.onCreateis now #2 (29 reports). That's the next crash lever if we want the remaining rate down further.cc #176
ErikBjare commentedon Sep 13, 2026
Fixed in #176 (comment)
ErikBjare commentedon Sep 13, 2026
@TimeToBuildBob File a issue for the remaining "ForegroundServiceStartNotAllowedException in BackgroundService.onCreate"
TimeToBuildBob commentedon Sep 13, 2026
Filed #255: #255
I re-queried live Play vitals before filing rather than copying the September 1 snapshot. This has grown from 29 reports to the current top crash on v0.14.0b2:
BackgroundService.onCreate/startForeground: 475 reports, 83 users in 7d (928 / 142 in 21d), still active through 2026-09-12AlarmReceiver.onReceive/startForegroundServicecluster: 7 reports, 5 users in 7d#255 carries both stacks, Play issue IDs, current code paths, and acceptance criteria. The leading cause is a disallowed background promotion during a sticky restart; the boot receiver is independently unguarded, so the issue deliberately covers both boundaries instead of claiming one unproven trigger.