Upload failures keep name, size and file readability #34

Merged
void merged 5 commits from void/upload-error-diagnostics into dev 2026-11-09 10:59:53 +00:00
Member

Meta cannot attach images from Android Chrome: UI shows MatrixError: Unknown message.

Observed in source: matrix-js-sdk 41.7 XHR upload throws DOMException(xhr.statusText, "AbortError") when status is 0. statusText is empty, our catch used e.message for errcode and kept the empty message, so MatrixError fell back to "Unknown message".

This PR only fixes the diagnosis. uploadFailure sets errcode from e.name, keeps cause, and the upload card message now carries name, size, type, and whether file.arrayBuffer() still reads the file after the failure.

Cause of the status-0 on Android is NOT established. Candidates (unobserved): unreadable content:// Blob, big body over VPN/MTU. The new message tells them apart on the next failure.

Tests: uploadFailure.test.ts (red before, 6 green after). Full suite: 249/250; soundscape.test.ts fails with window is not defined, a file this PR does not touch, not checked against dev.

🤖 Generated with Claude Code

Added after Meta's phone traces (head 25765c44)

  • Failure line now carries: connectivity, tab visibility, idle ms, first progress/bytes, ms to failure, per-attempt upload XHR end state (readyState, status, events fired with timings, signal.aborted), abort() call stacks (page-made aborts; none observed so far), and the owning component's renders/unmount after attach.
  • Every attempt uploads an in-memory copy of the file (ArrayBuffer read first); an unreadable file fails as "file unreadable before upload". Memory cost: one extra copy of the file (limit 50 MB on the server).
  • One automatic retry when an attempt aborts within 3s with no progress and no user cancel.
  • None of this is shown to fix the failures; the cause is unobserved.

Follow-up, not touched here

UploadCardRenderer.tsx:125-127 (and CompactUploadCardRenderer.tsx) call startUpload() during render while the upload status is Idle. Status leaves Idle only when onPromise sets the promise, so a render that happens first (e.g. StrictMode double render) can start the same upload twice. Not shown to be happening; not an abort path. Left as is on purpose.

Meta cannot attach images from Android Chrome: UI shows `MatrixError: Unknown message`. Observed in source: matrix-js-sdk 41.7 XHR upload throws `DOMException(xhr.statusText, "AbortError")` when status is 0. statusText is empty, our catch used `e.message` for errcode and kept the empty message, so MatrixError fell back to "Unknown message". This PR only fixes the diagnosis. `uploadFailure` sets errcode from `e.name`, keeps `cause`, and the upload card message now carries name, size, type, and whether `file.arrayBuffer()` still reads the file after the failure. Cause of the status-0 on Android is NOT established. Candidates (unobserved): unreadable content:// Blob, big body over VPN/MTU. The new message tells them apart on the next failure. Tests: uploadFailure.test.ts (red before, 6 green after). Full suite: 249/250; soundscape.test.ts fails with `window is not defined`, a file this PR does not touch, not checked against dev. 🤖 Generated with [Claude Code](https://claude.com/claude-code) ## Added after Meta's phone traces (head 25765c44) - Failure line now carries: connectivity, tab visibility, idle ms, first progress/bytes, ms to failure, per-attempt upload XHR end state (readyState, status, events fired with timings, signal.aborted), abort() call stacks (page-made aborts; none observed so far), and the owning component's renders/unmount after attach. - Every attempt uploads an in-memory copy of the file (ArrayBuffer read first); an unreadable file fails as "file unreadable before upload". Memory cost: one extra copy of the file (limit 50 MB on the server). - One automatic retry when an attempt aborts within 3s with no progress and no user cancel. - None of this is shown to fix the failures; the cause is unobserved. ## Follow-up, not touched here UploadCardRenderer.tsx:125-127 (and CompactUploadCardRenderer.tsx) call startUpload() during render while the upload status is Idle. Status leaves Idle only when onPromise sets the promise, so a render that happens first (e.g. StrictMode double render) can start the same upload twice. Not shown to be happening; not an abort path. Left as is on purpose.
Upload failures keep their name, size and file readability
Some checks failed
Build pull request / Build pull request (pull_request) Has been cancelled
252fabc138
The catch in uploadContent set errcode from e.message and dropped an empty
message, so the SDK's status-0 AbortError (DOMException with empty message)
surfaced as "MatrixError: Unknown message". uploadFailure now uses e.name for
errcode, keeps the original as cause, and puts size, type and whether the
file can still be read into the message the upload card shows.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Upload failure text carries what the client can observe
Some checks failed
Build pull request / Build pull request (pull_request) Failing after 3m5s
95f0dce05f
Adds connectivity (onLine, effectiveType, rtt, downlink, saveData), tab visibility, ms since
last user activity, time to first progress and bytes loaded, and ms to failure. Nothing patches
XMLHttpRequest; the progress handler and window input events are the only sources.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Retry an upload once when it aborts at once with no progress
Some checks failed
Build pull request / Build pull request (pull_request) Failing after 3m12s
be947a0bf9
An AbortError within 3s of starting, with no progress event and no user cancel, is retried once;
the failed attempt discards its connection, so the retry cannot reuse it. The final failure text
says "retried" with attempt 1's timing. Never retried after progress, a user cancel, a
different error, or a slow abort. The cause (a reused dead keep-alive socket) is unproven.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Upload failure text carries who called abort and what the owning component did
Some checks failed
Build pull request / Build pull request (pull_request) Has been cancelled
3e57865eca
For each upload attempt, XMLHttpRequest.prototype.abort and AbortController.prototype.abort are
wrapped (reference-counted across overlapping attempts, restored in finally) and every call is
recorded with a six-frame file:line:col stack; "abort calls: none" says the page called neither.
The upload hook also counts renders after attach (and those within 300ms) and any unmount.
The in-memory copy and the once-only retry are unchanged.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Upload each attempt from an in-memory copy; failure line names the XHR's end state
Some checks failed
Build pull request / Build pull request (pull_request) Failing after 3m12s
25765c44c5
- before every attempt the picked file is read into memory (ArrayBuffer) and that copy is what
  the SDK uploads; a file the browser cannot read fails there as "file unreadable before upload"
- while an attempt runs, XMLHttpRequest open/send are wrapped (reference-counted with the abort
  probe, restored in finally, no behaviour change) so the failure line carries each upload XHR's
  method, path (query dropped), readyState, status, the events it fired with timings, and our
  signal.aborted
- the abort-call stacks stay as a minor extra

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
void merged commit 9ab8bbeb33 into dev 2026-11-09 10:59:53 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
GameCult/Ratatoskr!34
No description provided.