fileDelete hangs ~143s then returns plain-text HTTP 500 when files in one call carry ~10k references — reproduced on a clean dev store

Summary

fileDelete consistently fails when the files in one mutation carry a large total number of references: the call hangs for ~143 seconds and then returns a transport-level HTTP 500 with a plain-text Internal Server Error body (not a GraphQL JSON error response). We first saw this in production on a merchant’s shop, and we have now reproduced it on a clean development store with a minimal recipe (below), including a request ID.

API version: 2026-04

The request

mutation fileDelete($fileIds: [ID!]!) {
    fileDelete(fileIds: $fileIds) {
        deletedFileIds
    }
}

Reproduction (clean development store)

  1. Create 60 products, each with 100 variants.
  2. Upload 60 small PNG files (staged upload + fileCreate), wait for READY.
  3. Attach file i to ALL 60 products (fileUpdate referencesToAdd) and append it to product i’s 100 variants (productVariantAppendMedia) → each file ends up with ~160 references, ~9,600 references across the batch.
  4. Call fileDelete with all 60 file ids in ONE mutation.

Result: request hangs 143.78s → HTTP 500, body Internal Server Error (plain text).
X-Request-ID: 2fa18c62-5d2f-4f30-9be7-ab23823be3c9-1781158853 (2026-06-11, development store)

The failure threshold is dose-dependent — same store, same mutation, increasing total references:

Total references in one fileDelete Composition Result
~121 (1 file) mostly variant refs OK in 3.84s
~300 (50 files) mixed OK in 9.56s
~5,850 (45 files) product refs only OK in 41.13s
~9,600 (60 files) variant-heavy 143.78s → HTTP 500

Two observations from the data: the per-reference cost is roughly linear (and variant references appear ~4-5x more expensive to unwind than product references), and the ~143s cutoff looks like a fixed server-side execution budget — production failures on the merchant’s shop died at 142.4–142.9s, identical to the repro.

Production occurrence

Merchant shop (domain available via DM), products with large variant matrices where each image is attached to many variants. Deleting one product’s media (chunks of up to 200 file ids) fails every time with the same signature. Failing calls observed 2026-06-10 at 06:20:32→06:22:55, 06:25:23→06:27:46, 06:30:14→06:32:36 UTC. Everything else on the shop works (queries, staged uploads, fileCreate, fileUpdate, productVariantAppendMedia) — only fileDelete fails.

This also matches the earlier unresolved report in fileDelete mutation limitations (files with 100+ references failing with “Internal error”, including a 490-reference file that could not be deleted through the Admin UI either).

Workaround we verified

Detaching the references first (fileUpdate with referencesToRemove) and then deleting files individually succeeds reliably — our repro’s cleanup deleted the exact same 60 files that the bulk call could not.

Questions

  1. Is there a documented (or documentable) limit on total reference-unwind work per fileDelete call?
  2. Is the ~143s hang + non-GraphQL plain-text 500 the intended failure mode? A fast GraphQL userError would let clients degrade gracefully instead of burning 2+ minutes per attempt.
  3. What is the recommended pattern for deleting reference-heavy files — smaller batches, detach-then-delete, or something else?

Hi @farid :wave: thanks for flagging and for the detailed writeup, that’s very helpful.

I did some digging and was able to find some previous reports of the issue. You’ve diagnosed it correctly. fileDelete unwinds all of a file’s references as part of the same request, and that work is roughly linear in the number of references (variant references really are more expensive to clean up than product ones). When the total reference count gets high enough, the call exceeds the server-side request time budget and the worker is terminated before it can hand back a structured response. We’re looking into this further.

On your questions:

  1. There isn’t a documented per-reference limit to point you at. fileDelete caps at 250 file ids per call, but that’s not the binding constraint here - the real ceiling is how much reference-unwind work fits inside one request before it times out.

  2. The slow hang followed by a non-GraphQL 500 isn’t an intended failure mode, and I agree a fast userError would be a much better experience. I’ve made sure that this feedback is heard although I don’t have a guarantee or timeline I can share yet.

  3. For now your verified approach is the one I’d recommend: detach references first with fileUpdate referencesToRemove, then delete the files in small batches or individually. Keeping each fileDelete call’s reference fan-out small is what keeps it under the budget.

I’ll follow up in this thread as soon as I have more information to share and I hope this helps for now!

Thanks for looking into this issue and for the answers @Wes-Dev-Shopify :folded_hands:

Hey @farid - following up on this as I did some more digging

It looks like This is indeed a confirmed known issue on our end. In practice very few requests reach that point, which is part of why it hasn’t been prioritized yet, so I don’t have a timeline I can share on a fix.

The team does want to address it though, and the suggestion of returning a userError rather than a 500 landed well as something that would make this much clearer to run into. I don’t have a timeline I can share here either, but I’ve left the feedback in the right place

For now the pattern you verified is the one I’d stick with: detach references first with fileUpdate referencesToRemove, then delete the files in small batches or individually. Keeping each call’s reference fan-out small is what keeps it under the budget.

I’ll come back to this thread if anything changes on the fix. Hope this helps!

Thanks @Wes-Dev-Shopify, good to know this is in backlog.