{"record":{"id":"91b4c2762e9d151d","repo":"hcengineering/platform","slug":"failed-to-abort-multipart-upload","errorCode":null,"errorMessage":"Failed to abort multipart upload","messagePattern":"Failed to abort multipart upload","errorType":"exception","errorClass":"DatalakeError","httpStatus":null,"severity":"warning","filePath":"foundations/server/packages/datalake/src/client.ts","lineNumber":478,"sourceCode":"      throw new DatalakeError('Failed to complete multipart upload')\n    }\n  }\n\n  private async multipartUploadAbort (\n    ctx: MeasureContext,\n    workspace: WorkspaceUuid,\n    objectName: string,\n    multipart: MultipartUpload\n  ): Promise<void> {\n    const path = `/upload/multipart/${workspace}/${encodeURIComponent(objectName)}/abort`\n    const url = new URL(concatLink(this.endpoint, path))\n    url.searchParams.set('uploadId', multipart.uploadId)\n\n    try {\n      await fetchSafe(ctx, url, { method: 'POST', headers: { ...this.headers } })\n    } catch (err: any) {\n      ctx.error('failed to abort multipart upload', { workspace, objectName, err })\n      throw new DatalakeError('Failed to abort multipart upload')\n    }\n  }\n}\n\nasync function toBuffer (data: Buffer | string | Readable): Promise<Buffer> {\n  if (Buffer.isBuffer(data)) {\n    return data\n  } else if (typeof data === 'string') {\n    return Buffer.from(data)\n  } else if (data instanceof Readable) {\n    const chunks: Buffer[] = []\n    for await (const chunk of data) {\n      chunks.push(chunk)\n    }\n    return Buffer.concat(chunks as any)\n  } else {\n    throw new TypeError('Unsupported data type')\n  }","sourceCodeStart":460,"sourceCodeEnd":496,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/foundations/server/packages/datalake/src/client.ts#L460-L496","documentation":"DatalakeError thrown when the POST to /upload/multipart/{workspace}/{object}/abort fails. Abort is the cleanup path used by uploadWithMultipart's catch block, so this error usually appears while another upload error is already propagating and indicates the server could not release the multipart session.","triggerScenarios":"uploadWithMultipart fails and calls multipartUploadAbort, but fetchSafe throws: network failure, 404 for an expired/never-created uploadId, or a server error on the abort endpoint.","commonSituations":"UploadId already expired or completed server-side; datalake service unreachable during error cleanup; stale session after a service restart.","solutions":["Treat as secondary: inspect the original upload error first (it precedes this one).","Log and continue - a 404 usually means the session is already gone and no cleanup is needed.","Verify the datalake service is reachable; orphaned sessions are typically garbage-collected server-side.","Check server logs for the abort request if sessions leak."],"exampleFix":"// before\ntry {\n  await upload()\n} catch (err) {\n  await client.abort(ctx, ws, name)\n}\n// after\ntry {\n  await upload()\n} catch (err) {\n  try {\n    await client.abort(ctx, ws, name)\n  } catch (abortErr) {\n    console.warn('abort failed, session may already be gone', abortErr)\n  }\n  throw err\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await client.abort(ctx, ws, name)\n} catch (abortErr) {\n  // cleanup failure is non-fatal; the original error matters more\n  console.warn('multipart abort failed', abortErr)\n}","preventionTips":["Always wrap abort in its own try/catch and never mask the original error.","Treat 404 on abort as success (session already gone).","Rely on server-side garbage collection for orphaned sessions.","Log the uploadId to help server-side cleanup."],"tags":["multipart-upload","cleanup","network"],"backgroundTag":"multipart-upload-failed","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}