Worker failures are indistinguishable from "nothing to say", and an answered reminder stays in the shade #73

Closed
opened 2026-08-20 21:24:14 -05:00 by null · 0 comments
Owner

What is true now. ReminderWorker.doWork (core/notifications/.../ReminderWorker.kt:58-68) catches every Exception and returns Result.success(). Result.retry() appears nowhere in the repository and no backoff is configured. A repository that throws every day produces no reminders, no logs, no metric, and nothing a user or a developer could tell apart from a quiet cycle. catch (Exception) also lets Error escape into CoroutineWorker, where it becomes a permanent failure for that run.

A failed recordCheckIn() AFTER a successful post (:147) is swallowed the same way: the notification was shown but never counted, so the stopping rule slips by one, silently.

Separately, setAutoCancel(true) (PeriodNotifier.kt:113) only covers the content tap, so tapping an action button leaves the reminder sitting in the shade to be tapped again; nothing cancels a reminder when the user answers in the app instead; and a forecast that moves does not withdraw an already-posted notification. The stop-asking notification is built inline (ReminderWorker.kt:104-116) bypassing NotificationCopy entirely and hardcoding PRIVATE visibility even for users who chose Direct — so it is the one notification no test in NotificationCopyTest/NotificationPrivacyTest covers.

What to do. Split transient failures (DataStore/SQLite IO) from deterministic ones: retry the first with backoff up to a small limit, end the second quietly. Catch Exception, not Throwable, so Errors still reach WorkManager. Post first and record second, so a failed record retries the run and re-posts to the same notification id with setOnlyAlertOnce(true) — one buzz, counted once. Move the stop-asking text into NotificationCopy so it obeys the privacy mode like everything else. Cancel the posted reminder when an action is applied and when the user answers in the app.

Traps. No logging on any of these paths — core/notifications is in modulesSeeingHealthData and checkNoHealthLogging forbids every logging call, which is why the failures are silent today. The in-app dismissal chain must skip its first emission, or the worker's own process will cancel the notification it is about to post.

Verify: a failed check-in write after posting asks for a retry and does not double count and a cancellation is not swallowed in ReminderWorkerTest; the stop-asking notice leaks nothing outside Direct in NotificationCopyTest; an in-app answer dismisses the posted reminder and the first emission at start does not dismiss anything in ReminderCoordinatorTest.

**What is true now.** `ReminderWorker.doWork` (`core/notifications/.../ReminderWorker.kt:58-68`) catches every `Exception` and returns `Result.success()`. `Result.retry()` appears nowhere in the repository and no backoff is configured. A repository that throws every day produces no reminders, no logs, no metric, and nothing a user or a developer could tell apart from a quiet cycle. `catch (Exception)` also lets `Error` escape into `CoroutineWorker`, where it becomes a permanent failure for that run. A failed `recordCheckIn()` AFTER a successful post (`:147`) is swallowed the same way: the notification was shown but never counted, so the stopping rule slips by one, silently. Separately, `setAutoCancel(true)` (`PeriodNotifier.kt:113`) only covers the content tap, so tapping an action button leaves the reminder sitting in the shade to be tapped again; nothing cancels a reminder when the user answers in the app instead; and a forecast that moves does not withdraw an already-posted notification. The stop-asking notification is built inline (`ReminderWorker.kt:104-116`) bypassing `NotificationCopy` entirely and hardcoding PRIVATE visibility even for users who chose Direct — so it is the one notification no test in `NotificationCopyTest`/`NotificationPrivacyTest` covers. **What to do.** Split transient failures (DataStore/SQLite IO) from deterministic ones: retry the first with backoff up to a small limit, end the second quietly. Catch `Exception`, not `Throwable`, so `Error`s still reach WorkManager. Post first and record second, so a failed record retries the run and re-posts to the same notification id with `setOnlyAlertOnce(true)` — one buzz, counted once. Move the stop-asking text into `NotificationCopy` so it obeys the privacy mode like everything else. Cancel the posted reminder when an action is applied and when the user answers in the app. **Traps.** No logging on any of these paths — `core/notifications` is in `modulesSeeingHealthData` and `checkNoHealthLogging` forbids every logging call, which is why the failures are silent today. The in-app dismissal chain must skip its first emission, or the worker's own process will cancel the notification it is about to post. Verify: `a failed check-in write after posting asks for a retry and does not double count` and `a cancellation is not swallowed` in `ReminderWorkerTest`; `the stop-asking notice leaks nothing outside Direct` in `NotificationCopyTest`; `an in-app answer dismisses the posted reminder` and `the first emission at start does not dismiss anything` in `ReminderCoordinatorTest`.
null added this to the Batch 12 — Reminders that tell the truth milestone 2026-08-20 21:24:14 -05:00
null added the
P2
label 2026-08-20 21:24:14 -05:00
null closed this issue 2026-08-21 01:06:16 -05:00
Sign in to join this conversation.
No Label
P0
P1
P2
release-blocker
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: null/Privacy-Period-Tracker#73
No description provided.