hcengineering/platform · info

Upload aborted

Error message

Upload aborted

What it means

uploadXhr checks the caller-supplied AbortSignal before creating the XMLHttpRequest and throws 'Upload aborted' immediately if signal.aborted is already true. It also rejects with the same message via xhr.onabort if the signal fires mid-upload. This is the library's cancellation mechanism, not an unexpected fault.

Source

Thrown at foundations/core/packages/storage-client/src/upload.ts:40

  method: 'POST' | 'PUT'
  headers: Record<string, string>
  body: XMLHttpRequestBodyInit
}

/** @public */
export interface XHRUploadResult {
  status: number
  responseText: string
}

/** @public */
export async function uploadXhr (upload: XHRUpload, options?: FileStorageUploadOptions): Promise<XHRUploadResult> {
  const signal = options?.signal
  const onProgress = options?.onProgress

  // Check if already aborted before starting
  if (signal?.aborted === true) {
    throw new Error('Upload aborted')
  }

  const xhr = new XMLHttpRequest()

  const abortHandler = (): void => {
    xhr.abort()
  }

  if (signal !== undefined) {
    signal.addEventListener('abort', abortHandler)
  }

  try {
    return await new Promise<XHRUploadResult>((resolve, reject) => {
      xhr.upload.onprogress = (event) => {
        if (event.lengthComputable) {
          onProgress?.({
            loaded: event.loaded,

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Create a fresh AbortController for each upload instead of reusing one across calls.
  2. Check signal.aborted before scheduling the upload and skip it if cancellation was intended.
  3. Catch this error and treat it as a normal user cancellation — do not retry.
  4. If the upload must survive component unmount, move the controller's ownership out of the effect's cleanup scope.

Example fix

// before: shared controller reused for many files, aborted by earlier cancel
const controller = new AbortController()
for (const f of files) await uploadFile(token, ws, id, f, { signal: controller.signal })

// after: one controller per upload
for (const f of files) {
  const controller = new AbortController()
  await uploadFile(token, ws, id, f, { signal: controller.signal })
}
Defensive patterns

Strategy: try-catch

Validate before calling

// skip the call entirely if cancellation already happened
if (options?.signal?.aborted === true) return
const controller = new AbortController() // fresh controller per upload

Type guard

function isUploadAborted (err: unknown): err is Error {
  return err instanceof Error && err.message === 'Upload aborted'
}

Try / catch

try {
  await uploadFile(token, ws, uuid, file, { signal })
} catch (err) {
  if (isUploadAborted(err)) return // expected user cancellation — don't retry
  throw err
}

Prevention

When it happens

Trigger: uploadXhr (directly or via uploadFile/uploadPromise) is called with options.signal that was already aborted, or an 'abort' event fires on the signal while the XHR is in flight.

Common situations: React/Vue effect cleanup calling AbortController.abort() on unmount while an upload is still pending; a user cancel button wired to a controller reused across uploads; awaiting an upload after its owning operation was already cancelled; reusing one signal for sequential uploads and passing an aborted one to the next.

Related errors


AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/da611e393649688e. Report an issue: GitHub.