yikart/AiToEarn · error · AppException

ResponseCode.S3DownloadFileFailed

ResponseCode.S3DownloadFileFailed

Error message

S3DownloadFileFailed

What it means

S3DownloadFileFailed is thrown by S3Service.putObjectFromUrl when it fetches the source URL and response.body is null, i.e. there is no readable body to stream into object storage. The service first checks if the object already exists; if not, it downloads from the URL and re-uploads it — a null body aborts that.

Source

Thrown at project/aitoearn-backend/libs/aws-s3/src/s3.service.ts:73

    const command = new HeadObjectCommand({
      Bucket: this.config.bucketName,
      Key: objectPath,
    })
    return await this.client.send(command)
  }

  async putObjectFromUrl(
    url: string,
    objectPath: string,
  ) {
    try {
      await this.headObject(objectPath)
      return { path: objectPath, exists: true }
    }
    catch {
      const response = await fetch(url)
      if (response.body === null) {
        throw new AppException(ResponseCode.S3DownloadFileFailed)
      }
      const contentType = response.headers.get('content-type') || undefined
      return this.putObject(objectPath, response.body, contentType)
    }
  }

  // 生成预签名上传 URL
  async getUploadSignPost(objectPath: string, contentType?: string) {
    const result = await createPresignedPost(this.signingClient, {
      Bucket: this.config.bucketName,
      Key: objectPath,
      Expires: this.config.signExpires,
      Conditions: contentType ? [['eq', '$Content-Type', contentType]] : undefined,
      Fields: contentType ? { 'Content-Type': contentType } : undefined,
    })
    return result
  }

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Verify the source URL is reachable and returns a 200 with a body (curl -I)
  2. Re-generate or refresh signed/expired source URLs
  3. Add retry with backoff around putObjectFromUrl for transient network failures
  4. Check the response status code before calling the endpoint and fail fast with a clearer error

Example fix

// before
await s3Service.putObjectFromUrl(url, path) // url expired
// after
const head = await fetch(url, { method: 'HEAD' })
if (!head.ok) throw new Error(`source unavailable: ${head.status}`)
await s3Service.putObjectFromUrl(url, path)
Defensive patterns

Strategy: retry

Validate before calling

const probe = await fetch(url, { method: 'HEAD' })
if (!probe.ok) throw new Error(`source URL not downloadable: ${probe.status}`)

Try / catch

try {
  await s3Service.putObjectFromUrl(url, path)
} catch (e) {
  if (e instanceof AppException && e.code === ResponseCode.S3DownloadFileFailed) {
    // retry with backoff or mark import as failed with source-URL context
  } else throw e
}

Prevention

When it happens

Trigger: The remote URL returns an empty/204 body, a redirect to an empty response, a network-layer failure surfacing as a body-less response, or the URL points at a resource that no longer exists (some servers return null body with error statuses that fetch doesn't throw on depending on setup).

Common situations: Importing an asset from a third-party URL that now 404s or redirects; expired signed URL for the source file; hotlink protection returning empty responses; timeout between fetch and body read.

Related errors


AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31). Data as JSON: /api/errors/5791c6fbb4e4dc23. Report an issue: GitHub.