Snapshot picked files into memory-backed Files to prevent request queue poisoning#96940
Draft
elirangoshen wants to merge 3 commits into
Draft
Snapshot picked files into memory-backed Files to prevent request queue poisoning#96940elirangoshen wants to merge 3 commits into
elirangoshen wants to merge 3 commits into
Conversation
…ning A File picked from disk only references its OS file. If that file is modified or deleted before the queued request is persisted (e.g. while the preview modal is open), the IndexedDB write fails with "DataError: Failed to write blobs" (InvalidBlob/IOError) and the dead File poisons the persisted request queue, failing every later rewrite. Copy the bytes into a memory-backed File at validation time, so the queued request can never reference a stale OS file. If the file is already unreadable at validation, surface FILE_INVALID to the user instead of failing silently later. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
eslint-seatbelt caps no-unsafe-type-assertion at the grandfathered count for this file; use Object.defineProperty instead of casting. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the arrayBuffer feature-detect with snapshotPickedFile/index.ts (web: copy bytes into a memory-backed File) and index.native.ts (keep the original rename-only behavior; native has no IndexedDB blob path). Snapshot tests move to their own file that pins the web implementation, since Jest resolves the .native variant by default. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
42 tasks
Contributor
|
|
elirangoshen
force-pushed
the
eliran/2397-snapshot-picked-files
branch
from
July 24, 2026 12:12
5974a7f to
04ed14c
Compare
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.
Explanation of Change
On web, a
Filepicked from disk (file picker, drag-and-drop) is only a lazy reference to its OS file. If the user modifies, deletes, renames, or moves that file after picking it but before the queued request is first persisted (for example while the attachment preview or the expense confirmation screen is open — whether by user action, a download still completing, or background software touching the file), the first IndexedDB write of the persisted request queue fails with:The dead
Filethen poisonsnetworkRequestQueue: the storage write is retried 5×, and every subsequent queue rewrite re-serializes the same dead blob and fails again. Production logs show ~46k of these events over 3 days acrossAddAttachment,ReplaceReceipt, andRequestMoney(SmartScan) — all failing on the very first persist (retryAttempt: 0/5), which pins the failure to the pick → first-write window.This PR snapshots the picked file's bytes into a memory-backed
FileinvalidateAttachmentFile(the single choke point all attachment/receipt flows go through viauseFilesValidation). A memory-backedFilecannot be invalidated by later disk changes, so the queued request always serializes. If the backing file is already unreadable at validation time, the user gets an immediate "file invalid" error instead of a silent failure later.The snapshot lives in a platform-split module (
src/libs/snapshotPickedFile/index.ts+index.native.ts): the web variant copies the bytes, the native variant keeps the existing rename-only behavior — native file objects never enter the IndexedDB blob path, and production logs show this error class exclusively from web user agents.Related follow-up (independent, no dependency): Expensify/react-native-onyx#814 classifies this error as never-retriable at the storage layer, so any remaining inline File/Blob path fails once instead of retrying 5x. It ships separately via #96957.
Fixed Issues
$ #87844
$ #87871
PROPOSAL:
Tests
+→ Add attachment and pick the file. Keep the preview modal open.Failed to write blobs/[Onyx] Failed to save to storageerrors.npx jest tests/unit/ValidateAttachmentFileTest.tsand verify all tests pass.Offline tests
Failed to write blobserrors appear in the console.QA Steps
Same as Tests (steps 1–7).
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectionAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari
before
before.mov
after
after.mov