fix(reporting): auto-prompt to submit a crash only on first re-open, for a legitimate <24h crash #255

Closed
opened 2026-07-03 18:57:39 +00:00 by JMR-dev · 0 comments
JMR-dev commented 2026-07-03 18:57:39 +00:00 (Migrated from github.com)

Problem

The automatic crash-report submit prompt over-triggers. After updating to a build that fixes a crash, the app still pops the "LibreMail crashed" dialog on the next launch — because pre-update crash reports remain in the file-backed store and are re-surfaced on every launch. It reads as "the new build just crashed," when it didn't.

Observed live (2026-07-03): a device on the pre-fix build crash-looped on the SQLCipher UnsatisfiedLinkError (4 saved crash reports at 09:02). After installing the fix (592a797) the process ran stably (8m+ uptime, no new crash anywhere — confirmed the two restarts were killDueToPackageUpdate + a task-removal kill, not crashes), yet on relaunch the dialog popped for those stale 09:02 reports.

Required behavior (product owner)

The automatic submit UX must:

  1. Trigger only on the FIRST app re-open after a crash — offered at most once, then it must not re-nag on subsequent launches. The report still remains in the Problem Reports list for manual review/submit.
  2. Only for a crash report < 24 hours old — older crashes never auto-pop.
  3. Only for a legitimate crash — an app update, a user-initiated close (swipe-away / task removal), or a force-stop must NOT be treated as a crash or trigger the prompt.

Current behavior / gap

  • StartupReportViewModel.pendingCrash (app/src/main/kotlin/org/libremail/ui/reporting/StartupReportViewModel.kt) surfaces the newest ReportKind.CRASH report on every launch until dismissed/discarded, with no age bound → re-nags and shows stale crashes.
  • Reports are created only by the uncaught-exception handler (CrashReporter), so update/force-stop/user-close already do not create a crash report. Requirement #3 is therefore mostly already satisfied by construction — but it must be made explicit and test-covered, and the surfacing gates (#1, #2) added.

Proposed implementation

  • First-re-open-only: persist a per-report "surfaced" marker (a flag on the stored DebugReport, or a small seen-id set) so a given crash is auto-offered at most once. dismiss() marks it surfaced (not just an in-memory hide). The report stays in Problem Reports.
  • <24h age gate: pendingCrash filters to createdAtMillis >= now - 24h.
  • Legitimate-crash distinction: verify + test that killDueToPackageUpdate, force-stop, and task-removal yield no crash report and no prompt. Optional hardening: a clean-shutdown sentinel (write a marker on normal stop/update; only surface a crash lacking a matching clean marker) if we want defense beyond the uncaught-handler guarantee.

Definition of done (per CLAUDE.md)

  • Unit tests (StartupReportViewModel): fresh (<24h) unseen crash → shown; same report after being surfaced once → NOT shown again across relaunches (first-open-only); crash ≥24h old → not shown; non-CRASH kinds unaffected; 24h boundary.
  • Instrumented/E2E: a legitimate recent crash pops the dialog exactly once and not again on the next launch; a stale/old report does not pop it.
  • Not done until unit and E2E pass.

Sequencing

Depends on #249 (coverage lane 4) landing first — that lane adds StartupReportViewModel unit tests, and this change modifies that VM + its tests, so implementing before #249 merges would collide semantically. Implement after #249 lands and update those tests to the new behavior.

## Problem The automatic crash-report submit prompt over-triggers. After updating to a build that **fixes** a crash, the app still pops the "LibreMail crashed" dialog on the next launch — because pre-update crash reports remain in the file-backed store and are re-surfaced on every launch. It reads as "the new build just crashed," when it didn't. **Observed live (2026-07-03):** a device on the pre-fix build crash-looped on the SQLCipher `UnsatisfiedLinkError` (4 saved crash reports at 09:02). After installing the fix (`592a797`) the process ran stably (8m+ uptime, no new crash anywhere — confirmed the two restarts were `killDueToPackageUpdate` + a task-removal kill, not crashes), yet on relaunch the dialog popped for those stale 09:02 reports. ## Required behavior (product owner) The automatic submit UX must: 1. **Trigger only on the FIRST app re-open after a crash** — offered at most once, then it must not re-nag on subsequent launches. The report still remains in the Problem Reports list for manual review/submit. 2. **Only for a crash report < 24 hours old** — older crashes never auto-pop. 3. **Only for a legitimate crash** — an app update, a user-initiated close (swipe-away / task removal), or a **force-stop** must NOT be treated as a crash or trigger the prompt. ## Current behavior / gap - `StartupReportViewModel.pendingCrash` (`app/src/main/kotlin/org/libremail/ui/reporting/StartupReportViewModel.kt`) surfaces the newest `ReportKind.CRASH` report on **every** launch until dismissed/discarded, with no age bound → re-nags and shows stale crashes. - Reports are created only by the uncaught-exception handler (`CrashReporter`), so update/force-stop/user-close already do **not** create a crash report. Requirement #3 is therefore mostly already satisfied by construction — but it must be made **explicit and test-covered**, and the surfacing gates (#1, #2) added. ## Proposed implementation - **First-re-open-only:** persist a per-report "surfaced" marker (a flag on the stored `DebugReport`, or a small seen-id set) so a given crash is auto-offered at most once. `dismiss()` marks it surfaced (not just an in-memory hide). The report stays in Problem Reports. - **<24h age gate:** `pendingCrash` filters to `createdAtMillis >= now - 24h`. - **Legitimate-crash distinction:** verify + test that `killDueToPackageUpdate`, force-stop, and task-removal yield no crash report and no prompt. Optional hardening: a clean-shutdown sentinel (write a marker on normal stop/update; only surface a crash lacking a matching clean marker) if we want defense beyond the uncaught-handler guarantee. ## Definition of done (per CLAUDE.md) - **Unit tests** (`StartupReportViewModel`): fresh (<24h) unseen crash → shown; same report after being surfaced once → NOT shown again across relaunches (first-open-only); crash ≥24h old → not shown; non-`CRASH` kinds unaffected; 24h boundary. - **Instrumented/E2E**: a legitimate recent crash pops the dialog exactly once and not again on the next launch; a stale/old report does not pop it. - Not done until unit **and** E2E pass. ## Sequencing Depends on **#249 (coverage lane 4)** landing first — that lane adds `StartupReportViewModel` unit tests, and this change modifies that VM + its tests, so implementing before #249 merges would collide semantically. Implement after #249 lands and update those tests to the new behavior.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: JMR-dev/LibreMail#255