{"record":{"id":"0fa8581677b51474","repo":"yikart/AiToEarn","slug":"responsecode-assetuploadfailed","errorCode":"ResponseCode.AssetUploadFailed","errorMessage":"AssetUploadFailed","messagePattern":"AssetUploadFailed","errorType":"error_code","errorClass":"AppException","httpStatus":null,"severity":"error","filePath":"project/aitoearn-backend/libs/assets/src/assets.service.ts","lineNumber":362,"sourceCode":"\n  async cleanupExpiredPendingAssets(olderThanSeconds: number): Promise<{ affectedCount: number }> {\n    return await this.assetRepository.markExpiredPendingAsFailed(olderThanSeconds)\n  }\n\n  async confirmUploadByUser(userId: string, assetId: string, userType: UserType = UserType.User): Promise<Asset> {\n    const asset = await this.assetRepository.getByIdAndUserId(assetId, userId, userType)\n\n    if (!asset) {\n      throw new AppException(ResponseCode.AssetNotFound)\n    }\n\n    if (asset.status !== AssetStatus.Pending) {\n      return asset\n    }\n\n    const headResult = await this.storage.headObject(asset.path)\n    if (!headResult) {\n      throw new AppException(ResponseCode.AssetUploadFailed)\n    }\n\n    if (this.options.maxSize != null && headResult.contentLength && headResult.contentLength >= this.options.maxSize) {\n      throw new AppException(ResponseCode.AssetTooLarge)\n    }\n\n    const expectedMimeType = asset.filename\n      ? (mime.lookup(asset.filename) || asset.mimeType)\n      : asset.mimeType\n    const currentContentType = headResult.contentType\n\n    if (expectedMimeType && currentContentType !== expectedMimeType) {\n      const contentDisposition = expectedMimeType.startsWith('video/') ? 'inline' : undefined\n      await this.storage.copyObject(asset.path, {\n        contentType: expectedMimeType,\n        contentDisposition,\n        metadata: {\n          assetId: asset.id,","sourceCodeStart":344,"sourceCodeEnd":380,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/libs/assets/src/assets.service.ts#L344-L380","documentation":"AssetUploadFailed is thrown by confirmUploadByUser when storage.headObject(asset.path) returns null, meaning the file the client was supposed to upload to object storage is not present at the expected key. The asset record exists (status Pending) but the actual bytes never arrived, so confirmation cannot proceed.","triggerScenarios":"Client marks upload done without actually PUTting the file; upload to S3/OSS failed or was aborted midway; the object was uploaded to a different path/key than asset.path; the object was deleted between upload and confirm; wrong storage bucket configured.","commonSituations":"Client SDK swallows upload errors and still calls confirm; presigned URL expired so the PUT never happened; bucket/prefix misconfiguration after an env change; multipart upload aborted due to network loss.","solutions":["Verify the client actually completed the storage upload before calling confirmUploadByUser","Re-run the upload with a fresh presigned URL if it expired","Check storage configuration (bucket, endpoint, credentials, path prefix) matches between upload and confirm","Retry the whole initiate→upload→confirm flow"],"exampleFix":"// before\nawait client.call('confirmUpload', assetId) // upload actually failed silently\n// after\nconst ok = await uploadToStorage(presignedUrl, file)\nif (!ok) throw new Error('upload failed, do not confirm')\nawait client.call('confirmUpload', assetId)","handlingStrategy":"retry","validationCode":"const head = await fetch(uploadUrl, { method: 'HEAD' }).catch(() => null)\nif (!head || !head.ok) throw new Error('object not present at storage path; re-upload first')","typeGuard":null,"tryCatchPattern":"try {\n  await assetsService.confirmUploadByUser(userId, assetId)\n} catch (e) {\n  if (e instanceof AppException && e.code === ResponseCode.AssetUploadFailed) {\n    await reUploadAndConfirm(assetId, file) // retry whole upload flow\n  } else throw e\n}","preventionTips":["Confirm client upload success (ETag/200) before calling confirm","Use fresh presigned URLs; do not cache across sessions","Keep bucket/prefix config consistent between services","Monitor storage-layer upload errors to catch silent client failures"],"tags":["upload","storage","s3"],"backgroundTag":"upload-verification-failed","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}