-
Notifications
You must be signed in to change notification settings - Fork 3.7k
[$250] App update clears Onyx data not synced with server / PERSISTED_REQUESTS must be preserved during Onyx.clear() #86710
Description
Split from https://github.com/Expensify/Expensify/issues/618149
Problem
When Onyx.clear() is called (e.g., via the RESET_REQUIRED listener), the PERSISTED_REQUESTS queue — which holds all offline API requests waiting to sync — is wiped because it is not in the KEYS_TO_PRESERVE list. This causes permanent loss of offline-created expenses that were never synced to the server.
Customers are reporting missing expenses after app updates. Investigation confirms these expenses were created offline and existed only in the Onyx cache / SequentialQueue.
Root Cause
There are three data loss vectors:
1. RESET_REQUIRED listener wipes offline queue
The listener at App.ts:154-168 calls Onyx.clear(KEYS_TO_PRESERVE) then openApp() without saving/restoring the SequentialQueue.
Contrast with clearOnyxAndResetApp() at App.ts:827-876 which correctly snapshots the queue first:
function clearOnyxAndResetApp() {
const sequentialQueue = getAll(); // ← Saves queue first
rollbackOngoingRequest();
Onyx.clear(KEYS_TO_PRESERVE).then(() => {
openApp().then(() => {
for (const request of sequentialQueue) { save(request); } // ← Restores queue
});
});
}2. Full reconnect replaces local state
When the server returns previousUpdateID=0 (triggered when clientUpdateID is stale or -1), a Pusher ReconnectApp event causes a full state replacement from the server — which knows nothing about expenses that were never synced.
3. Processing window vulnerability
In PersistedRequests.ts:255-284, when a request starts processing, it's removed from PERSISTED_REQUESTS and held only in memory. Expense creation commands do not set persistWhenOngoing, so if the app is killed during this window, the request is lost.
Recommended Fix
-
Add
ONYXKEYS.PERSISTED_REQUESTSandONYXKEYS.PERSISTED_ONGOING_REQUESTSto theKEYS_TO_PRESERVElist inApp.ts:122-146, OR modify theRESET_REQUIREDlistener to snapshot/restore the queue the same wayclearOnyxAndResetApp()already does. -
Set
persistWhenOngoing: truefor expense creation commands (REQUEST_MONEY,TRACK_EXPENSE,SPLIT_BILL_AND_OPEN_REPORT, etc.) so in-flight requests survive app kills.
Relevant Code
App.ts:122-168—KEYS_TO_PRESERVElist +RESET_REQUIREDlistenerApp.ts:827-876—clearOnyxAndResetApp(correct pattern for queue preservation)PersistedRequests.ts:107-147,255-284— Request persistence + processing windowNetwork/enhanceParameters.ts:11—clientUpdateIDdefaults to -1SignInRedirect.ts:47-105— AnotherOnyx.clear()path that doesn't preserve the queue
Implementation Notes
- Adding
PERSISTED_REQUESTStoKEYS_TO_PRESERVEis the simplest fix for theRESET_REQUIREDpath, but care must be taken to ensure the queue is processed correctly afteropenApp()refreshes the Onyx state (since the optimistic data keys the queue references will have been cleared) - The
persistWhenOngoingflag already exists in the infrastructure — expense creation commands just need to opt into it
Issue Owner
Current Issue Owner: @ShridharGoelUpwork Automation - Do Not Edit
- Upwork Job URL: https://www.upwork.com/jobs/~022039242963579817769
- Upwork Job ID: 2039242963579817769
- Last Price Increase: 2026-04-01
Metadata
Metadata
Assignees
Labels
Type
Projects
Status