{"record":{"id":"da611e393649688e","repo":"hcengineering/platform","slug":"upload-aborted","errorCode":null,"errorMessage":"Upload aborted","messagePattern":"Upload aborted","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"info","filePath":"foundations/core/packages/storage-client/src/upload.ts","lineNumber":40,"sourceCode":"  method: 'POST' | 'PUT'\n  headers: Record<string, string>\n  body: XMLHttpRequestBodyInit\n}\n\n/** @public */\nexport interface XHRUploadResult {\n  status: number\n  responseText: string\n}\n\n/** @public */\nexport async function uploadXhr (upload: XHRUpload, options?: FileStorageUploadOptions): Promise<XHRUploadResult> {\n  const signal = options?.signal\n  const onProgress = options?.onProgress\n\n  // Check if already aborted before starting\n  if (signal?.aborted === true) {\n    throw new Error('Upload aborted')\n  }\n\n  const xhr = new XMLHttpRequest()\n\n  const abortHandler = (): void => {\n    xhr.abort()\n  }\n\n  if (signal !== undefined) {\n    signal.addEventListener('abort', abortHandler)\n  }\n\n  try {\n    return await new Promise<XHRUploadResult>((resolve, reject) => {\n      xhr.upload.onprogress = (event) => {\n        if (event.lengthComputable) {\n          onProgress?.({\n            loaded: event.loaded,","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/foundations/core/packages/storage-client/src/upload.ts#L22-L58","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Create a fresh AbortController for each upload instead of reusing one across calls.","Check signal.aborted before scheduling the upload and skip it if cancellation was intended.","Catch this error and treat it as a normal user cancellation — do not retry.","If the upload must survive component unmount, move the controller's ownership out of the effect's cleanup scope."],"exampleFix":"// before: shared controller reused for many files, aborted by earlier cancel\nconst controller = new AbortController()\nfor (const f of files) await uploadFile(token, ws, id, f, { signal: controller.signal })\n\n// after: one controller per upload\nfor (const f of files) {\n  const controller = new AbortController()\n  await uploadFile(token, ws, id, f, { signal: controller.signal })\n}","handlingStrategy":"try-catch","validationCode":"// skip the call entirely if cancellation already happened\nif (options?.signal?.aborted === true) return\nconst controller = new AbortController() // fresh controller per upload","typeGuard":"function isUploadAborted (err: unknown): err is Error {\n  return err instanceof Error && err.message === 'Upload aborted'\n}","tryCatchPattern":"try {\n  await uploadFile(token, ws, uuid, file, { signal })\n} catch (err) {\n  if (isUploadAborted(err)) return // expected user cancellation — don't retry\n  throw err\n}","preventionTips":["Create one AbortController per upload; never share across uploads.","Check signal.aborted before initiating any upload.","In component cleanup, only abort uploads owned by that component instance.","Treat 'Upload aborted' as success-path cancellation in UI code."],"tags":["upload","abort","cancellation","xhr"],"backgroundTag":"upload-aborted-signal","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}