Repository navigation
[Due for payment 2026-10-13] [Sentry: APP-B65] No fallback when indexedDB is unavailable, causing a rejection retry loop #100844
Description
Sentry
- https://expensify.sentry.io/issues/APP-B65 —
ReferenceError: Can't find variable: indexedDB
Impact (snapshot at filing, 2026-09-10)
| Sentry ID | Users (last 14d) | Users (total since first seen) | Events | First seen |
|---|---|---|---|---|
| APP-B65 | 8 | 24 | 16,934 | 2026-04-24 |
- Last seen: 2026-09-10 (ongoing)
- Platform: web (
standalone), Chrome Mobile on iOS - App version(s): present across 9.4.61-4 through 9.4.74-2 — not a recent regression
- Mechanism:
auto.browser.global_handlers.onunhandledrejection, levelerror - Affected view(s): not view-specific; the sampled event landed on
Settings_Rules
The event-to-user ratio is the important number here: roughly 700 events per affected user. A single session produces hundreds of rejections, so this issue alone accounts for a large share of total error volume even though few users hit it.
Error
ReferenceError: Can't find variable: indexedDB
Stack trace (top frames)
app:///10042537-100413459.js:5095:107328 (lu)
app:///10042537-100413459.js:5095:108205 (tg)
app:///10042537-100413459.js:5095:8423 (S)
app:///10042537-100413459.js:5095:158371 (Xp)
app:///10042537-100413459.js:5095:162388 (run)
[native code] (Promise)
The frames are minified. Source maps are not resolving for this bundle, so there are no readable function names — see the second task below.
Suspected cause
The storage layer assumes window.indexedDB exists. On Chrome for iOS the global is absent in some configurations — private browsing and Lockdown Mode are the known cases — so the very first storage call throws a ReferenceError rather than a rejected open request.
There is no fallback and no cached result of the failure, so every subsequent read and write throws the same way. That produces the retry loop visible in the event count. The user is left on an app that renders but cannot persist anything, with no message explaining why.
Two separate pieces of work:
- Detect and degrade. Probe for
indexedDBonce at storage init. When it is missing, fall back to the in-memory orlocalStorageprovider that Onyx already supports, record the decision once, and stop retrying. Surface a message telling the user that data will not persist in this browser mode. - Fix source maps for this bundle. The stack has no first-party frames, which is why this sat untriaged. Worth confirming the web release upload step is attaching maps for all bundles, not just the main chunk. This overlaps with Upload Android NDK debug symbols, which covers the native side of the same gap.
Reproduction
- Open Chrome on iOS and switch to a private tab, or enable Lockdown Mode.
- Load
https://new.expensify.comand sign in. - Open the browser console and verify repeated
Can't find variable: indexedDBrejections. - Verify that no error message is shown to the user, and that state does not survive a reload.
Related
- Source map and symbol coverage: [Sentry: APP-38K / APP-K4S / APP-77J] Upload Android NDK debug symbols — top native crashes are unsymbolicated #99848
- Parent tracker: [Tracking] Top Sentry crashes monthly check-in #91302
Issue Owner
Current Issue Owner: @mallenexpensifyMetadata
Metadata
Labels
Type
Projects
- StatusShow more project fieldsDone
Activity
melvin-bot commentedon Sep 10, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @getusha (
External)melvin-bot commentedon Sep 10, 2026
Unable to auto-create job on Upwork. The BZ team member should create it manually for this issue.
MelvinBot commentedon Sep 10, 2026
Proposal
What is the root cause of that problem?
Onyx has a "degrade to memory-only storage" fallback, but on this failure mode it is unreachable dead code.
Storage.init()only degrades whenprovider.init()throws the exact stringIDBKeyVal store could not be created(lib/storage/index.ts:43-44). That throw fires only whencreateStore()returnsnull(IDBKeyValProvider/index.ts:40-45). But Onyx'screateStore()is lazy — it never touchesindexedDBand unconditionally returns a closure, so it can never returnnull. Init always "succeeds", even in a browser with no IndexedDB at all.indexedDBis first read much later, on the first real operation, as a bare unguarded global (createStore.ts:59). On WebKit (Chrome for iOS is WKWebView) the global is genuinely absent in private/Lockdown configurations, so this is aReferenceError, not a rejected open request — the messageCan't find variable: indexedDBis WebKit's wording for an undeclared identifier, which rules out "IndexedDB present but broken".That error class is then unrecognised everywhere downstream, which is what turns one failure into 16,934 events:
classifyIDBErrorhas no branch for it, so it falls through toUNKNOWN(classifyError.ts:49).UNKNOWNmeans "bounded retry" — 5 retries per write,MAX_STORAGE_OPERATION_RETRY_ATTEMPTS(OnyxUtils.ts:854-865) — so every write costs 6 rejections and none of them ever succeed.Storage.getAllKeys()/getItem/multiGetre-reject with no.catchat their call sites (for exampleOnyxUtils.ts:476), which is exactly theonunhandledrejectionmechanism Sentry tagged.What changes do you think we should make in order to solve the problem?
The fix belongs in
Expensify/react-native-onyx, not in App — App only needs the version bump afterwards (currently pinned toreact-native-onyx@3.0.111inpackage.json:203).IDBKeyValProvider.init(), checktypeof indexedDB === 'undefined'(atypeofguard, so it does not itself throw) before returning the store, and throw the sentinel the existing fallback already listens for. This makesdegradePerformance()fire on the very first call and swap the provider toMemoryOnlyProvider— no new fallback machinery needed, just making the existing one reachable.typeofguard insidegetDB()so a mid-session loss can't produce the bareReferenceError.classifyIDBErroran explicit branch for a missing/unavailable IndexedDB that returns a non-retryable class. Today an unrecognised error costs 6 attempts, so any future variant of this bug re-amplifies 6× before anyone notices.Separately, the source-map claim in the issue doesn't hold up — worth splitting out rather than fixing here. The web build already uploads maps for every chunk, not just the main one (
assets: './dist/**/*.{js,map}',rsbuild.common.ts:579-580), and the build-time and runtime release strings match (${pkg.name}@${pkg.version}in bothrsbuild.common.ts:570andsetupSentry.ts:59). The stack frame10042537-100413459.jsdoes not match the current production naming scheme[name]-[contenthash].bundle.js(rsbuild.common.ts:377), so these frames most likely come from cached or pre-rsbuild bundles rather than a broken upload step. Confirm against a recent release before spending work here.What alternative solutions did you explore? (Optional)
Alternatives considered and ruled out
localStorageinstead of memory (as the issue suggests). Chrome iOS incognito also restrictslocalStorage, so it would need the same guard and would mostly reintroduce the problem.MemoryOnlyProvideralready exists and is what the current fallback path targets — use it.indexedDBreference atsrc/libs/ExportOnyxState/index.ts:11. It's worth guarding, but it is Troubleshoot-page-only and cannot produce hundreds of events per session, so it is not this issue.Can't find variable: Xis aReferenceErrorfor an undeclared identifier. A present-but-broken IDB would surface as a rejected open request with aDOMException.createStorecallsdocument.addEventListener('visibilitychange', …), which would itself throw in a worker.Related issues
UNKNOWNclassification blind spot; a fix here should feed that.UnknownError: Attempt to generate key in database without an in-progress transactionstorage error #100046 and [Due for payment 2026-09-21] [Onyx] InvestigateConstraintError: Key already exists in the object storestorage error #100047 — other unclassified Onyx storage errors following the same retry-amplification pattern.Not duplicates — none of them cover the missing-
indexedDBcase or the unreachable fallback.Next Steps for Contributor+ team:
To accept:
@MelvinBot implement [this](https://github.com/Expensify/App/issues/100844)to create a draft PR.To refine:
@MelvinBot <your feedback>To reject: Explain why you are rejecting Melvin's proposal.
sosek108 commentedon Sep 15, 2026
The fix proposed by Melvin is proper one. There is my draft PR with implementation Expensify/react-native-onyx#845.
Should we also tell the user about the situation? I don't recall any popup informing about storage errors in the App so I'm not sure
cc: @mountiny @fabioh8010
sosek108 commentedon Sep 17, 2026
Expensify/react-native-onyx#845 is open for review
melvin-bot commentedon Sep 22, 2026
@sosek108 Huh... This is 4 days overdue. Who can take care of this?
17 remaining items
mountiny commentedon Oct 8, 2026
All right, let's monitor that this issue has been fixed. Thank you!
melvin-bot commentedon Oct 12, 2026
Triggered auto assignment to @mallenexpensify (
Awaiting Payment)melvin-bot commentedon Oct 12, 2026
Payment Summary
Resolving PRs:
[Onyx bump] Bump react-native-onyx to 3.0.115 #101085
Contributor: @sosek108 does not require payment (Contractor)
Assignee: @getusha requires payment (Needs manual offer from BZ)
BugZero Checklist (@mallenexpensify)
mallenexpensify commentedon Oct 15, 2026
Payment Summary
Contributor+: @getusha due $250 via NewDot