Files
bookshelf/logs/J-crashsafe.summary
T
Spriteandclaude 34097269c9 Lookup path can't crash the app; persist crash reports for off-device reading
The wave-7 build crashed on the first scan on a real phone and never reproduced.
A live off-device run of the real lookup (93546ed) found nothing wrong with the
parse/merge code. What it did find: nothing on that path was exception-safe. Both
clients caught only IOException, classify caught only serialization errors, and
ScanViewModel.runLookup had no guard at all, so any other throwable — platform
TLS, an OkHttp internal, anything this JVM can't reproduce — killed the process
instead of surfacing on the scan sheet.

- OpenLibraryClient and GoogleBooksClient catch Throwable -> new
  FailureKind.UNEXPECTED, never retried. The reason names the exception CLASS
  only; its message can carry the request URL and therefore the API key.
- ScanViewModel.runLookup guards the same way, which also covers its Room call.
- CancellationException is rethrown ahead of every catch-all: dismissing the
  sheet cancels the lookup, and that must not render as a failure.
- diagnostics.CrashReporter persists uncaught stack traces and chains to the
  previously installed handler in a finally, so the process still dies normally
  even if writing the report fails.
- Settings gains a Diagnostics section: last crash, View trace, Share.

Not fixed, flagged by the worker: performSave/performSaveManualEntry make the
same unguarded Room calls on the save path.

Verified by the orchestrator: assembleDebug (--rerun-tasks) exit 0;
testDebugUnitTest exit 0, 205 tests (was 190), 2 skipped, 0 failures, counted
from TEST-*.xml; verifyPaparazziDebug exit 0; 0 "always 'false'" warnings.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQb3d68LqWD4EdND3C8jkD
2026-09-13 11:31:00 +00:00

16 lines
1.9 KiB
Plaintext

=== J-crashsafe attempts=1 quota_waits=0 hard_fails=0 ===
cost=$4.0241814 turns=111 err=?
--- result (tail) ---
tingsStore.setLastShelfId`. A Room/disk failure during *save* (as opposed to *lookup*) can still crash the process. The task scoped this fix to the lookup path specifically ("Note that `bookRepository.findByIsbn13(isbn13)` is inside this function too... This is deliberately belt-and-braces with §1"), so I left `performSave` alone rather than expanding scope unasked — but it's the same failure class and worth a future wave if it matters.
**What I could not do / did differently**
- Switched `LocalClipboardManager`/`AnnotatedString` to the non-deprecated `LocalClipboard`/`ClipEntry` API for the Copy action, since the deprecated path threw a fresh compiler warning on a file I was actively touching (instructions call out exactly this class of warning as not-cosmetic). Small deviation from the most obvious implementation, done to avoid leaving a new warning behind.
- No new dependencies needed; nothing in `build.gradle.kts` or `libs.versions.toml` touched.
- Did not touch `data/local`, `data/remote`, `data/repo`, `data/prefs`, or `server/`. `AppContainer.kt`/`BookshelfApplication.kt` edits are exactly the `crashReporter` wiring §3 needs.
**Anything I noticed that looks wrong but is out of scope**
- As noted above, `performSave`/`performSaveManualEntry` (the *save* path, not lookup) has the same unguarded-Room-call shape that `runLookup` had before this fix. Same failure class, different function.
- `ScanViewModel.manualIsbnEntered` and `retryLookup` both do `viewModelScope.launch { onScanned(...) / runLookup(...) }` — fire-and-forget launches. Since `runLookup` no longer lets anything but `CancellationException` escape, and `viewModelScope` cancellation is exactly what should happen there, this is fine as-is; flagging only because it's adjacent code I read closely.