Source: "same-class crash-state" audit (follow-up to the SQLCipher cold-start crash, #210).
PruneWorker and BackfillWorker are the only two pre-auth background DB entry points that do NOT gate on EncryptedCacheGuard.isCacheLocked() before opening the database — unlike SyncWorker, SendWorker, and IdleService, which inject their DB deps as dagger.Lazy and bail (Result.retry() / stopSelf()) when the cache is locked.
Trigger:encryptCache = true AND appLock = true (auth-sealed passphrase) on a headless cold start where the user hasn't authenticated (WorkManager wakes the process after reboot, or the periodic backfill (30 min) / prune (12 h) window fires while locked):
PruneWorker.kt:16-26 injects MailPruner directly (not Lazy) and calls pruner.prune() with no guard → accountDao.getAll() (MailPruner.kt:41) → runBlocking { prepareCache() } (DatabaseModule.kt:84) → resolvePassphrase → session.await() (PassphraseSession.kt:60) parks the worker thread until unlock.
BackfillWorker.kt:19-34 same via MailBackfiller.runBackfill() → accountDao.getAll() (MailBackfiller.kt:70).
Blast radius: wasted wakelock/battery each cycle while locked, zero prune/backfill progress during the locked window, and the held prepareCache mutex serializes other openers behind the parked thread. Self-heals on unlock (not a crash / data-loss), but it's exactly the thread-park SyncWorker.kt:24-26 documents its guard to prevent.
Fix: switch MailPruner / MailBackfiller injection in both workers to dagger.Lazy, and add if (cacheGuard.isCacheLocked()) return Result.retry() before resolving the dep — mirroring SyncWorker / SendWorker. Test coverage tracked in the sibling unit + E2E tickets.
**Source:** "same-class crash-state" audit (follow-up to the SQLCipher cold-start crash, #210).
`PruneWorker` and `BackfillWorker` are the **only** two pre-auth background DB entry points that do NOT gate on `EncryptedCacheGuard.isCacheLocked()` before opening the database — unlike `SyncWorker`, `SendWorker`, and `IdleService`, which inject their DB deps as `dagger.Lazy` and bail (`Result.retry()` / `stopSelf()`) when the cache is locked.
**Trigger:** `encryptCache = true` AND `appLock = true` (auth-sealed passphrase) on a headless cold start where the user hasn't authenticated (WorkManager wakes the process after reboot, or the periodic backfill (30 min) / prune (12 h) window fires while locked):
- `PruneWorker.kt:16-26` injects `MailPruner` directly (not `Lazy`) and calls `pruner.prune()` with no guard → `accountDao.getAll()` (`MailPruner.kt:41`) → `runBlocking { prepareCache() }` (`DatabaseModule.kt:84`) → `resolvePassphrase` → `session.await()` (`PassphraseSession.kt:60`) **parks the worker thread until unlock**.
- `BackfillWorker.kt:19-34` same via `MailBackfiller.runBackfill()` → `accountDao.getAll()` (`MailBackfiller.kt:70`).
**Blast radius:** wasted wakelock/battery each cycle while locked, zero prune/backfill progress during the locked window, and the held `prepareCache` mutex serializes other openers behind the parked thread. Self-heals on unlock (not a crash / data-loss), but it's exactly the thread-park `SyncWorker.kt:24-26` documents its guard to prevent.
**Fix:** switch `MailPruner` / `MailBackfiller` injection in both workers to `dagger.Lazy`, and add `if (cacheGuard.isCacheLocked()) return Result.retry()` before resolving the dep — mirroring `SyncWorker` / `SendWorker`. Test coverage tracked in the sibling unit + E2E tickets.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Source: "same-class crash-state" audit (follow-up to the SQLCipher cold-start crash, #210).
PruneWorkerandBackfillWorkerare the only two pre-auth background DB entry points that do NOT gate onEncryptedCacheGuard.isCacheLocked()before opening the database — unlikeSyncWorker,SendWorker, andIdleService, which inject their DB deps asdagger.Lazyand bail (Result.retry()/stopSelf()) when the cache is locked.Trigger:
encryptCache = trueANDappLock = true(auth-sealed passphrase) on a headless cold start where the user hasn't authenticated (WorkManager wakes the process after reboot, or the periodic backfill (30 min) / prune (12 h) window fires while locked):PruneWorker.kt:16-26injectsMailPrunerdirectly (notLazy) and callspruner.prune()with no guard →accountDao.getAll()(MailPruner.kt:41) →runBlocking { prepareCache() }(DatabaseModule.kt:84) →resolvePassphrase→session.await()(PassphraseSession.kt:60) parks the worker thread until unlock.BackfillWorker.kt:19-34same viaMailBackfiller.runBackfill()→accountDao.getAll()(MailBackfiller.kt:70).Blast radius: wasted wakelock/battery each cycle while locked, zero prune/backfill progress during the locked window, and the held
prepareCachemutex serializes other openers behind the parked thread. Self-heals on unlock (not a crash / data-loss), but it's exactly the thread-parkSyncWorker.kt:24-26documents its guard to prevent.Fix: switch
MailPruner/MailBackfillerinjection in both workers todagger.Lazy, and addif (cacheGuard.isCacheLocked()) return Result.retry()before resolving the dep — mirroringSyncWorker/SendWorker. Test coverage tracked in the sibling unit + E2E tickets.