Skip to content

fix: prevent permanent Android startup crash from ObjectBox corruption and malformed server URL#3048

Open
richardtru wants to merge 3 commits into
BlueBubblesApp:developmentfrom
richardtru:richardtru/bug/android-startup-crash-recovery
Open

fix: prevent permanent Android startup crash from ObjectBox corruption and malformed server URL#3048
richardtru wants to merge 3 commits into
BlueBubblesApp:developmentfrom
richardtru:richardtru/bug/android-startup-crash-recovery

Conversation

@richardtru

Copy link
Copy Markdown

Summary

This fixes a recurring crash pattern that has affected multiple users over several years: BlueBubbles works correctly for a few days then permanently fails to start (every launch crashes until app data is cleared). Investigation of GitHub issues (#2845 and related) revealed two independent crash paths:

Root Cause 1 — ObjectBox database corruption (primary)

After a phone OOM kill or power-off during a write transaction, the ObjectBox store files in <app_docs>/objectbox/ become corrupted. On the next launch, openStore() throws an exception. Previously there was no recovery path — the late final Store store field was never assigned, causing LateInitializationError on every subsequent access, permanently bricking the app.

Fix: Added one-shot corruption recovery to both _initDatabaseMobile and _initDatabaseDesktop: on any store open failure that isn't a concurrent-access race, the corrupted directory is renamed to a timestamped backup (objectbox_corrupted_<ms>) and a fresh store is created. Up to 2 backups are retained for diagnostics; older ones are pruned automatically.

Also moved StartupTasks.initStartupServices() inside the try/catch in main.dart so that if recovery exhausts all options, the user sees the FailureToStart widget instead of a silent blank screen.

After recovery, Database.wasRecovered is set to true and onStartup() emits a database-recovered event so the UI can surface a "Syncing messages…" notice explaining the empty chat list.

Root Cause 2 — Malformed server URL crashes the foreground service (issue #2845)

If the stored server URL contains bare % characters (e.g. %s%), socket.io's Java URLDecoder throws IllegalArgumentException in a background thread outside any try/catch in the foreground service, crashing the process. Since the foreground service starts on every launch when keepAppAlive is enabled, the app permanently crashes on every startup.

Fix: Validate the URL with java.net.URI() (synchronous, no I/O) before handing it to IO.socket(). A URISyntaxException updates the notification to "missing server URL" and returns gracefully.

Security improvements (found during review)

  • Server URL no longer logged to logcat (contained auth credentials in query params)
  • Custom header values no longer logged — only header keys are logged
  • Socket.IO connect-error log no longer includes the server URL

Test plan

  • Normal startup: Clear app data, configure server, confirm app works and "Recovery successful" does NOT appear in logs
  • Simulate corruption (Android): adb shell am force-stop com.bluebubbles.messaging, then truncate <app_docs>/objectbox/data.mdb, relaunch → app recovers with empty chat list and begins syncing; objectbox_corrupted_* backup dir is present
  • Malformed URL (issue Cannot Open Android App #2845): Manually set flutter.serverAddress to http://192.168.1.1:1234/%s%test in SharedPreferences, launch → foreground service logs "Server URL stored in preferences is malformed", notification updates, no crash
  • Recovery failure path: Corrupt DB and set app directory to read-only → FailureToStart widget shown (not silent blank screen)
  • Backup pruning: Repeat corruption 3+ times → confirm at most 2 objectbox_corrupted_* directories exist

- add one-shot corruption recovery in _initDatabaseMobile and _initDatabaseDesktop:
  back up the corrupted objectbox dir and retry with a fresh store, preventing
  the "permanent blank screen after days" pattern caused by oom kills or power-off
  mid-write-transaction
- move initStartupServices inside the try/catch in main.dart so any startup failure
  (including recovery exhaustion) shows the FailureToStart widget instead of a
  silent blank screen
- validate server url with java.net.URI before passing to socket.io to prevent the
  IllegalArgumentException crash on malformed percent-encoding (github issue BlueBubblesApp#2845)
- extract _openOrAttachStore, _recoverCorruptedDatabase, _pruneCorruptedBackups as
  dedicated helpers; add _maxBackups constant; use async list() instead of listSync()
- remove server url from logcat lines to prevent credential leakage
- log only custom header keys (not values) to prevent auth token leakage
- fix desktop linux: move exit(0) inside concurrent-access branch so corrupted
  databases on linux actually get recovery rather than silent instance-exit
- add Database.wasRecovered flag + onStartup emission of database-recovered event
  so ui can show a sync notice when the database was wiped by recovery
- sort corrupted backup dirs by extracted timestamp int instead of lexicographic
  string comparison to correctly order backups when clock could drift
makes the validation path visible in logcat during manual testing
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant