Skip to content

[Due for payment 2026-10-16] [$175] Endless spinner when uploading image #102470

Closed

Description

@m-natarajan

If you haven’t already, check out our contributing guidelines for onboarding. To join our Slack channel, fill out this form.


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:

  1. Navigate to a Dm
  2. Click + > Add attachment
  3. Select the file
  4. 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

View all open jobs on GitHub

Issue OwnerCurrent Issue Owner: @QichenZhu
Upwork Automation - Do Not Edit

Activity

  1. added
    BugSomething is broken. Auto assigns a BugZero manager.
    on Sep 28, 2026
  2. MelvinBot commented on Sep 28, 2026

    @MelvinBot
    Contributor

    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 its uri, so the preview has nothing to load:

    1. getImageManipulator returns the resized File with a blob uri.
    2. resizeImageIfNeeded then passes it through createFile. On web, createFile builds a new File and drops uri. The native branch keeps it.
    3. ReportAddAttachmentModalContent now uses file.uri as it arrives. The carousel view turns the missing value into source: ''.
    4. expo-image renders an <img> with no src, which fires neither load nor error. ImageView only hides the spinner after onLoad, 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 blob uri. That hid the createFile bug.

    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?

    1. 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.
    2. 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.


    view run · no recording available

  3. added
    ExternalAdded to denote the issue can be worked on by a contributor
    on Sep 28, 2026
  4. melvin-bot commented on Sep 28, 2026

    @melvin-bot

    Current assignee @QichenZhu is eligible for the External assigner, not assigning anyone new.

  5. changed the title [-]Endless spinner when uploading image[/-] [+][$175] Endless spinner when uploading image[/+] on Sep 28, 2026
  6. melvin-bot commented on Sep 28, 2026

    @melvin-bot
  7. bonnhatnguyen commented on Sep 28, 2026

    @bonnhatnguyen

    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?

    1. 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.
    2. Immediately following the resize operation, resizeImageIfNeeded passes the resized file through createFile(resizedFile, fileName, fileType) (lines 690–697).
    3. 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.
    4. When ReportAddAttachmentModalContent maps the attachment files into carousel slides (AddAttachmentModalCarouselView), file.uri is undefined, resulting in source: ''.
    5. In ImageView (and Lightbox), expo-image rendering an image element with an empty string src never dispatches onLoad or onError.
    6. 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?

    1. 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):
      const newFile = new File([file], fileName, {type: fileType});
      newFile.uri = file.uri ?? URL.createObjectURL(newFile);
      return newFile;

      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.

    2. 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.
    3. 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.

    Contributor details
    Your Expensify account email: bonnhatnguyen0@gmail.com
    Upwork Profile Link: https://www.upwork.com/freelancers/~0139ed4098bdc50182

  8. melvin-bot commented on Sep 28, 2026

    @melvin-bot

    ✅ Contributor details stored successfully. Thank you for contributing to Expensify!

  9. bconnnnn commented on Sep 28, 2026

    @bconnnnn

    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.

  10. mallenexpensify commented on Sep 29, 2026

    @mallenexpensify
    Contributor

    I think the large-image resize path is the likely cause here.

    @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

    Image

    then, I checked and realized it was 27mb. @MelvinBot what's max size for image files in NewDot?

  11. MelvinBot commented on Sep 29, 2026

    @MelvinBot
    Contributor

    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.


    view run

  12. bconnnnn commented on Sep 29, 2026

    @bconnnnn

    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 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?

    1. 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.
    2. 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.
    3. 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.

  13. QichenZhu commented on Sep 29, 2026

    @QichenZhu
    Contributor

    @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.

  14. 38 remaining items

  15. trjExpensify commented on Oct 19, 2026

    @trjExpensify
    Contributor

    approved $175 for @QichenZhu.

  16. m-natarajan commented on Oct 19, 2026

    @m-natarajan
    Author

    @mallenexpensify @grgia @QichenZhu Able to reproduce this issue and

    Attachment error
    Image dimensions are too large to process. Please use a smaller image.
    

    modal displayed for a file size of 29.3 mb.

    Recording.3714.mp4
  17. mallenexpensify commented on Oct 19, 2026

    @mallenexpensify
    Contributor

    @QichenZhu 👀 above plz

  18. QichenZhu commented on Oct 19, 2026

    @QichenZhu
    Contributor

    @m-natarajan, mind sharing the image with me?

  19. m-natarajan commented on Oct 19, 2026

    @m-natarajan
    Author

    @QichenZhu The same image Matt used to report this, Shared via Slack

  20. QichenZhu commented on Oct 19, 2026

    @QichenZhu
    Contributor
    Image

    @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

  21. melvin-bot commented on Oct 26, 2026

    @melvin-bot

    @QichenZhu Huh... This is 4 days overdue. Who can take care of this?

  22. QichenZhu commented on Oct 26, 2026

    @QichenZhu
    Contributor

    I think we can close this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Awaiting PaymentAuto-added when associated PR is deployed to productionBugSomething is broken. Auto assigns a BugZero manager.DailyKSv2ExternalAdded to denote the issue can be worked on by a contributor

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions