You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Due for payment 2026-10-16] [$175] Endless spinner when uploading image #102470
Version Number: 9.4.95-0 Reproducible in staging?: Y Reproducible in production?: Y If this was caught during regression testing, add the test name, ID and link from BrowserStack: Email or phone of affected tester (no customers): Logs:https://stackoverflow.com/c/expensify/questions/4856 Expensify/Expensify Issue URL: Issue reported by:@mallenexpensify Slack conversation (hyperlinked to channel name): #Expensify Bugs
Action Performed:
Navigate to a Dm
Click + > Add attachment
Select the file
Observe the preview screen
Expected Result:
Image displayed in preview
Actual Result:
Loading spinner displayed and unable to send the image
Note:
Issue reproducible only on the particular image and not with others
Workaround:
Unknown
Platforms:
Select the officially supported platforms where the issue was reproduced:
Android: App
Android: mWeb Chrome
iOS: App
iOS: mWeb Safari
iOS: mWeb Chrome
Windows: Chrome
MacOS: Chrome / Safari
Screenshots/Videos
Recording.3603.mp4
File to upload : Is in Slack thread and unable to attach to the GH report
Confidence: medium. I traced the code from end to end, but I couldn't reproduce it in a browser. I also can't see the reporter's image. This cause fits only if that image is larger than 24 MiB. Please check its size in the Slack thread.
What is the root cause of that problem?
On web, images over 24 MiB (MAX_SIZE = 25,165,824 bytes) get resized before the preview opens. The resized file loses its uri, so the preview has nothing to load:
Most images are under 24 MiB and skip resizing, which explains why only this one image fails.
What changes do you think we should make in order to solve the problem?
In resizeImageIfNeeded, keep the resized file's uri after createFile. You could also have the web branch of createFile copy file.uri, or create one with URL.createObjectURL.
Add a unit test: on web, resizeImageIfNeeded returns a File with a blob uri. The current tests in useFilesValidation.test.ts mock the resize with files that already have a uri, so they miss this bug.
What alternative solutions did you explore? (Optional)
In useFilesValidation, after resizing, set uri on any File that lacks one. This fixes every caller at once.
As a safeguard, have ImageView and Lightbox treat an empty url as an error, so the preview shows the error view instead of spinning.
How to verify, and other possible causes
How to reproduce: On web, open a DM and add a PNG or JPG over 24 MiB and under 50 MP, for example a 5000x4000 noise PNG. The preview should spin forever, and in devtools the preview <img> should have no src. A 20 MB image should preview normally.
If the reporter's image is 24 MiB or smaller, this cause doesn't apply. These are other places where the promise can hang with no timeout (not verified):
expo-image-manipulator loadImageAsync throws inside img.onload when the canvas context is null, so its promise never settles.
react-native-web ImageLoader.getSize never calls back when the natural dimensions stay 0. validateImageForCorruption uses it.
The HEIC converter worker has no timeout.
Related issues: none found in a quick search.
Next Steps for Contributor+ team: To accept:@MelvinBot implement [this](https://github.com/Expensify/App/issues/102470) to create a draft PR. To refine:@MelvinBot <your feedback> To reject: Explain why you are rejecting Melvin's proposal.
Please re-state the problem that we are trying to solve in this issue.
When attempting to attach a large image in a DM chat on Web/Desktop, the attachment modal shows a continuous loading spinner and the user is unable to preview or send the image.
What is the root cause of that problem?
In src/libs/fileDownload/FileUtils.ts, resizeImageIfNeeded checks if an image file exceeds MAX_SIZE (24 MiB / 25,165,824 bytes). For files larger than MAX_SIZE, it calls getImageManipulator().manipulateAsync, which resizes the image and returns a File object containing a valid blob uri.
Immediately following the resize operation, resizeImageIfNeeded passes the resized file through createFile(resizedFile, fileName, fileType) (lines 690–697).
In createFile (lines 676–688):
On native platforms, file.uri is preserved on the returned object.
On Web, createFile constructs a new browser File via new File([file], fileName, {type: fileType}) without transferring the uri property from the resized file.
When ReportAddAttachmentModalContent maps the attachment files into carousel slides (AddAttachmentModalCarouselView), file.uri is undefined, resulting in source: ''.
In ImageView (and Lightbox), expo-image rendering an image element with an empty string src never dispatches onLoad or onError.
Because ImageView relies strictly on onLoad / onError callbacks to dismiss the loading state (isImageLoading), the spinner spins indefinitely.
What changes do you think we should make in order to solve the problem?
In src/libs/fileDownload/FileUtils.ts:
In createFile, ensure that on Web the returned File object retains the uri property from the source file (or creates a valid object URL if missing):
Alternatively, inside resizeImageIfNeeded, preserve resizedFile.uri on the file instance returned by createFile. This guarantees that large resized images retain their valid blob uri for previewing and uploading.
In src/components/ImageView/index.tsx:
Add defensive handling for empty or invalid source URLs: if url / source is empty, do not indefinitely maintain the loading state; transition out of isImageLoading and display the error/fallback view so the UI never hangs indefinitely.
In tests/unit/useFilesValidationTest.ts (or tests/unit/FileUtilsTest.ts):
Add a unit test asserting that on Web, when an image exceeding MAX_SIZE is processed through resizeImageIfNeeded, the returned File retains a non-empty, accessible uri property.
What alternative solutions did you explore? (Optional)
Re-running validateAttachmentFile in the modal: Explored and rejected because repeatedly validating and regenerating object URLs inside the modal was specifically eliminated in PR Prevent OOM tab reload when attaching multiple files on mWeb #93234 to avoid Out-Of-Memory (OOM) browser tab crashes on mobile web.
Only handling the empty URL check in ImageView: While this stops the infinite spinner, it does not resolve the root cause that the image cannot be previewed because the blob uri was dropped during resizing. Implementing both the root fix in createFile and the defensive check in ImageView provides a complete, robust fix.
I think the large-image resize path is the likely cause here.
On web, images over MAX_SIZE are resized and then passed through createFile(). The resized object has a blob uri, but the web branch creates a new browser File and drops that custom uri.
That leaves the attachment preview with no source, which matches the endless spinner.
The one thing I’d confirm before changing anything is that the reporter’s failing image is actually above the resize threshold. If it is, I’d preserve the resized uri in the web path and add a regression test around a large image going through resizeImageIfNeeded().
If the file is below the threshold, then this RCA is wrong and I’d look at the file-specific decode/dimension path instead.
mallenexpensify The server limit for chat attachments is 24 MiB (25,165,824 bytes). Receipt images have a lower limit of 10 MiB (CONST/index.ts:423-426).
Images over 24 MiB aren't meant to be rejected. The App should shrink them before upload, as long as they're 50 megapixels or less (MAX_IMAGE_PIXEL_COUNT). Your 27 MB image went down that resize path, which is where the bug is. The resized file loses its preview uri in createFile, so the preview spins forever.
That confirms the cause in my earlier proposal and in bconnnnn's comment. Expected behavior after the fix: the image previews and sends as a smaller copy.
Please re-state the problem that we are trying to solve in this issue.
On Web/Desktop, attaching the reporter's 27 MB image to a DM leaves the attachment preview spinning indefinitely, so the user cannot preview and send it.
What is the root cause of that problem?
The issue thread confirms the image is 27 MB, above the 24 MiB chat-attachment limit. On Web, resizeImageIfNeeded() in FileUtils.ts resizes an oversized image and passes the result through createFile(). getImageManipulator() assigns the resized File a blob URL in its uri, but the Web branch of createFile() creates a new browser File without copying that custom property. AddAttachmentModalCarouselView then uses an empty string when file.uri is missing, leaving the preview image without a usable source and its loading indicator active.
This matches the issue discussion and current code path; I have not reproduced the UI locally.
What changes do you think we should make in order to solve the problem?
Preserve the resized file's existing uri when createFile() wraps it for Web, or keep the original File object returned by the manipulator. Reuse the existing blob URL so its current lifecycle remains intact.
Add a unit test that runs an image larger than MAX_SIZE through the real resizeImageIfNeeded() path with a mocked manipulator result. Assert that the returned file retains the non-empty uri. The current hook tests mock resizeImageIfNeeded() and do not exercise this path.
As a UI safeguard, show an error state if the attachment preview receives an empty source; this should not replace preserving the URI.
What alternative solutions did you explore? (Optional)
Creating another object URL in the preview would hide the lost-property bug and add URL lifecycle management. Preserving the URI already produced by the resize operation is the smaller fix.
@MelvinBot, your main solution works in my testing on all platforms, but I'm not fully convinced by the RCA of the offending PR. It's not clear which is the more fundamental cause: whether createFile should omit uri on web platforms, or whether the user of the file should assume uri always exists.
Activity
MelvinBot commentedon Sep 28, 2026
Proposal
Confidence: medium. I traced the code from end to end, but I couldn't reproduce it in a browser. I also can't see the reporter's image. This cause fits only if that image is larger than 24 MiB. Please check its size in the Slack thread.
What is the root cause of that problem?
On web, images over 24 MiB (
MAX_SIZE= 25,165,824 bytes) get resized before the preview opens. The resized file loses itsuri, so the preview has nothing to load:getImageManipulatorreturns the resizedFilewith a bloburi.resizeImageIfNeededthen passes it throughcreateFile. On web,createFilebuilds a newFileand dropsuri. The native branch keeps it.ReportAddAttachmentModalContentnow usesfile.urias it arrives. The carousel view turns the missing value intosource: ''.<img>with nosrc, which fires neitherloadnorerror.ImageViewonly hides the spinner afteronLoad, so it spins forever.This is a regression from Prevent OOM tab reload when attaching multiple files on mWeb, merged 2026-07-16. Before that PR, the modal re-ran
validateAttachmentFile, which always set a fresh bloburi. That hid thecreateFilebug.Most images are under 24 MiB and skip resizing, which explains why only this one image fails.
What changes do you think we should make in order to solve the problem?
resizeImageIfNeeded, keep the resized file'suriaftercreateFile. You could also have the web branch ofcreateFilecopyfile.uri, or create one withURL.createObjectURL.resizeImageIfNeededreturns aFilewith a bloburi. The current tests inuseFilesValidation.test.tsmock the resize with files that already have auri, so they miss this bug.What alternative solutions did you explore? (Optional)
useFilesValidation, after resizing, seturion anyFilethat lacks one. This fixes every caller at once.ImageViewandLightboxtreat an emptyurlas an error, so the preview shows the error view instead of spinning.How to verify, and other possible causes
How to reproduce: On web, open a DM and add a PNG or JPG over 24 MiB and under 50 MP, for example a 5000x4000 noise PNG. The preview should spin forever, and in devtools the preview
<img>should have nosrc. A 20 MB image should preview normally.If the reporter's image is 24 MiB or smaller, this cause doesn't apply. These are other places where the promise can hang with no timeout (not verified):
loadImageAsyncthrows insideimg.onloadwhen the canvas context is null, so its promise never settles.ImageLoader.getSizenever calls back when the natural dimensions stay 0.validateImageForCorruptionuses it.Related issues: none found in a quick search.
Next Steps for Contributor+ team:
To accept:
@MelvinBot implement [this](https://github.com/Expensify/App/issues/102470)to create a draft PR.To refine:
@MelvinBot <your feedback>To reject: Explain why you are rejecting Melvin's proposal.
view run · no recording available
melvin-bot commentedon Sep 28, 2026
Current assignee @QichenZhu is eligible for the External assigner, not assigning anyone new.
[-]Endless spinner when uploading image[/-][+][$175] Endless spinner when uploading image[/+]melvin-bot commentedon Sep 28, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022104698178927868514
bonnhatnguyen commentedon Sep 28, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
When attempting to attach a large image in a DM chat on Web/Desktop, the attachment modal shows a continuous loading spinner and the user is unable to preview or send the image.
What is the root cause of that problem?
src/libs/fileDownload/FileUtils.ts,resizeImageIfNeededchecks if an image file exceedsMAX_SIZE(24 MiB / 25,165,824 bytes). For files larger thanMAX_SIZE, it callsgetImageManipulator().manipulateAsync, which resizes the image and returns aFileobject containing a valid bloburi.resizeImageIfNeededpasses the resized file throughcreateFile(resizedFile, fileName, fileType)(lines 690–697).createFile(lines 676–688):file.uriis preserved on the returned object.createFileconstructs a new browserFilevianew File([file], fileName, {type: fileType})without transferring theuriproperty from the resized file.ReportAddAttachmentModalContentmaps the attachment files into carousel slides (AddAttachmentModalCarouselView),file.uriisundefined, resulting insource: ''.ImageView(andLightbox),expo-imagerendering an image element with an empty stringsrcnever dispatchesonLoadoronError.ImageViewrelies strictly ononLoad/onErrorcallbacks to dismiss the loading state (isImageLoading), the spinner spins indefinitely.What changes do you think we should make in order to solve the problem?
In
src/libs/fileDownload/FileUtils.ts:createFile, ensure that on Web the returnedFileobject retains theuriproperty from the sourcefile(or creates a valid object URL if missing):Alternatively, inside
resizeImageIfNeeded, preserveresizedFile.urion the file instance returned bycreateFile. This guarantees that large resized images retain their valid bloburifor previewing and uploading.In
src/components/ImageView/index.tsx:url/sourceis empty, do not indefinitely maintain the loading state; transition out ofisImageLoadingand display the error/fallback view so the UI never hangs indefinitely.In
tests/unit/useFilesValidationTest.ts(ortests/unit/FileUtilsTest.ts):MAX_SIZEis processed throughresizeImageIfNeeded, the returnedFileretains a non-empty, accessibleuriproperty.What alternative solutions did you explore? (Optional)
validateAttachmentFilein the modal: Explored and rejected because repeatedly validating and regenerating object URLs inside the modal was specifically eliminated in PR Prevent OOM tab reload when attaching multiple files on mWeb #93234 to avoid Out-Of-Memory (OOM) browser tab crashes on mobile web.ImageView: While this stops the infinite spinner, it does not resolve the root cause that the image cannot be previewed because the bloburiwas dropped during resizing. Implementing both the root fix increateFileand the defensive check inImageViewprovides a complete, robust fix.Contributor details
Your Expensify account email: bonnhatnguyen0@gmail.com
Upwork Profile Link: https://www.upwork.com/freelancers/~0139ed4098bdc50182
melvin-bot commentedon Sep 28, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!
bconnnnn commentedon Sep 28, 2026
I think the large-image resize path is the likely cause here.
On web, images over
MAX_SIZEare resized and then passed throughcreateFile(). The resized object has a bloburi, but the web branch creates a new browserFileand drops that customuri.That leaves the attachment preview with no source, which matches the endless spinner.
The one thing I’d confirm before changing anything is that the reporter’s failing image is actually above the resize threshold. If it is, I’d preserve the resized
uriin the web path and add a regression test around a large image going throughresizeImageIfNeeded().If the file is below the threshold, then this RCA is wrong and I’d look at the file-specific decode/dimension path instead.
mallenexpensify commentedon Sep 29, 2026
@bconnnnn you might be onto something. For some reason I didn't think the file size was the problem but when I went to upload it in the OP here I got
then, I checked and realized it was 27mb. @MelvinBot what's max size for image files in NewDot?
MelvinBot commentedon Sep 29, 2026
mallenexpensifyThe server limit for chat attachments is 24 MiB (25,165,824 bytes). Receipt images have a lower limit of 10 MiB (CONST/index.ts:423-426).Images over 24 MiB aren't meant to be rejected. The App should shrink them before upload, as long as they're 50 megapixels or less (
MAX_IMAGE_PIXEL_COUNT). Your 27 MB image went down that resize path, which is where the bug is. The resized file loses its previewuriincreateFile, so the preview spins forever.That confirms the cause in my earlier proposal and in
bconnnnn's comment. Expected behavior after the fix: the image previews and sends as a smaller copy.view run
bconnnnn commentedon Sep 29, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
On Web/Desktop, attaching the reporter's 27 MB image to a DM leaves the attachment preview spinning indefinitely, so the user cannot preview and send it.
What is the root cause of that problem?
The issue thread confirms the image is 27 MB, above the 24 MiB chat-attachment limit. On Web,
resizeImageIfNeeded()in FileUtils.ts resizes an oversized image and passes the result throughcreateFile(). getImageManipulator() assigns the resizedFilea blob URL in itsuri, but the Web branch ofcreateFile()creates a new browserFilewithout copying that custom property. AddAttachmentModalCarouselView then uses an empty string whenfile.uriis missing, leaving the preview image without a usable source and its loading indicator active.This matches the issue discussion and current code path; I have not reproduced the UI locally.
What changes do you think we should make in order to solve the problem?
uriwhencreateFile()wraps it for Web, or keep the originalFileobject returned by the manipulator. Reuse the existing blob URL so its current lifecycle remains intact.MAX_SIZEthrough the realresizeImageIfNeeded()path with a mocked manipulator result. Assert that the returned file retains the non-emptyuri. The current hook tests mockresizeImageIfNeeded()and do not exercise this path.What alternative solutions did you explore? (Optional)
Creating another object URL in the preview would hide the lost-property bug and add URL lifecycle management. Preserving the URI already produced by the resize operation is the smaller fix.
QichenZhu commentedon Sep 29, 2026
@MelvinBot, your main solution works in my testing on all platforms, but I'm not fully convinced by the RCA of the offending PR. It's not clear which is the more fundamental cause: whether
createFileshould omiturion web platforms, or whether the user of the file should assumeurialways exists.38 remaining items
trjExpensify commentedon Oct 19, 2026
approved $175 for @QichenZhu.
m-natarajan commentedon Oct 19, 2026
@mallenexpensify @grgia @QichenZhu Able to reproduce this issue and
modal displayed for a file size of 29.3 mb.
Recording.3714.mp4
mallenexpensify commentedon Oct 19, 2026
@QichenZhu 👀 above plz
QichenZhu commentedon Oct 19, 2026
@m-natarajan, mind sharing the image with me?
m-natarajan commentedon Oct 19, 2026
@QichenZhu The same image Matt used to report this, Shared via Slack
QichenZhu commentedon Oct 19, 2026
@m-natarajan, the dimension of the image (top) is 74 megapixels (13583 x 5417 pixels), which exceeds the app's 50-megapixel limit.
The image in Matt's bug report (bottom) is a different one.
cc @mallenexpensify
melvin-bot commentedon Oct 26, 2026
@QichenZhu Huh... This is 4 days overdue. Who can take care of this?
QichenZhu commentedon Oct 26, 2026
I think we can close this issue.