fix(mobile): wait out SQLite locks and use WAL in encrypted store - #6090
Merged
Conversation
Surface: the mobile app (apps/mobile). Problem: Sentry KILO-APP-7K (https://kilo-code.sentry.io/issues/7707832591/) reports `Call to function 'NativeStatement.runSync' has been rejected. -> Caused by: Error code : database is locked`, mixed frames with the in-app frame at apps/mobile/src/lib/persist/encrypted-kv.ts (26 events, 2 users, last seen 2026-09-11). The same error is the project's largest unresolved crash: KILO-APP-5J (https://kilo-code.sentry.io/issues/7686568573/, 748 events, 84 users) and KILO-APP-5H (https://kilo-code.sentry.io/issues/7686568565/, 66 events, 23 users), both system-only frames at expo-sqlite's NativeStatement.runSync. Every write in the encrypted store goes through that call: setItem (encrypted-kv.ts:265-268), removeItem (275-277), clearScope (284), clearScopePrefix (291-293), plus the probe and migration (167-170). Cause evidence: the connection is opened and keyed but never configured to wait out a lock. `openWithKey` calls `SQLite.openDatabaseSync(DATABASE_NAME)` (encrypted-kv.ts:152) and, after `assertSQLCipher` (154) and `PRAGMA key` (155), returns the Drizzle handle with no `PRAGMA busy_timeout` and no `PRAGMA journal_mode = WAL`. In SQLite's default rollback-journal mode a synchronous write that meets a held lock fails immediately with SQLITE_BUSY instead of waiting. The store also swallows a failed close: `closeQuietly` catches and discards every error (132-138), and the recovery path then calls `deleteDatabaseAsync` and reopens over a h
Contributor
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Files Reviewed (2 files)
Reviewed by deepseek-v4.1-flash · Input: 0 · Output: 0 · Cached: 0 Review guidance: REVIEW.md from base branch |
Contributor
Author
|
Removed |
eshurakov
approved these changes
Sep 14, 2026
This was referenced Sep 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Changelog for users
database is lockedwhile another connection holds the lock; the encrypted store waits up to five seconds instead of failing immediately.Changelog for maintainers
PRAGMA cipher_versionprobe,PRAGMA key,PRAGMA busy_timeout = 5000,PRAGMA journal_mode = WALverified from the returned row, then the first schema probe and migrations.BUSY_TIMEOUT_MSis 5000; a WAL switch that does not returnwalthrows and fails the open.closeQuietlynow returns whether the handle closed, and recovery aborts delete-and-recreate when the previous handle stays open.error.subsystem=encrypted-kv,error.operation=reset, then rethrows.MissingSQLCipherError, delete-and-recreate when close succeeds, plus schema, migrations, storage keys, exported API, and dependencies.encrypted-kv.test.tsgainsjournalModeandfailCloseseams covering the SQL order, the 5000 ms value, a rejected WAL switch, and abort-on-unclosed-handle.E2E proof
[e2] Android dev build: contention drive, no lock, busy_timeout/WAL before probe
android emulator-5554: 5 relaunches, pragmas
busy_timeout = 5000andjournal_mode -> wallogged exactly 5 times (once per open) beforePROBE first sqlite_master statement; 71 writes (45 drafts),write_rejected=0,lock_lines=0,nativerun_lines=0(e2-contention.log); a same-device pre-fix baseline run did not reproduce the lock (e2-contention-baseline.ctllock_lines=0), and no baseline stack/udid was provided; no UX surface is rendered by the changed code, screenshots e2.png/e2-home.png captured for the visual reviewer.[e1] iOS dev build: contention drive, no lock, busy_timeout/WAL before probe
Proven on android emulator-5554 (the iOS-labelled scenario is proven on the only platform this host runs): 5 relaunches each logged once-before-probe order
[ekv-instr] open=1 PRAGMA busy_timeout = 5000->PRAGMA journal_mode -> wal->PROBE first sqlite_master statement; drive did 73 writes (45 drafts),write_rejected=0,lock_lines=0,nativerun_lines=0(e1-contention.log); screenshots e1.png for the visual reviewer.[p2] Android dev build: the same contention drive and the same no-lock plus busy_timeout/WAL pragma assertions.
Android emulator-5554: 6 opens each logged
PRAGMA busy_timeout = 5000andPRAGMA journal_mode = WAL/PRAGMA journal_mode -> walbeforePROBE first sqlite_master statement, withopens=6,write_ok=88,draft_writes=54,lock_lines=0,nativerun_lines=0; the scripted-scene miss was a start-state artifact and the scenario was re-driven from a cold launch, no visible surface is introduced by this storage-only diff (p2.png captured for the visual reviewer).Owner request