yikart/AiToEarn · error · AppException
S3DownloadFileFailed
S3DownloadFileFailed
Error message
S3DownloadFileFailed
What it means
createVideo fetches the input_reference image URL before uploading it to OpenAI. If the fetch returns a non-OK HTTP status (404, 403, timeout page, etc.), the service throws AppException(ResponseCode.S3DownloadFileFailed) because the reference image could not be downloaded for the video generation request.
Source
Thrown at project/aitoearn-backend/apps/aitoearn-ai/src/core/ai/video/openai/openai.service.ts:82
return { id: result.id }
}
/**
* OpenAI 视频创建
*/
async createVideo(request: UserOpenAIVideoCreateRequestDto) {
const { userId, userType, model, prompt, input_reference, seconds, size } = request
const startedAt = new Date()
// 如果 input_reference 是 URL,需要先 fetch 后传入 Response
let inputReferenceUploadable: Response | undefined
if (input_reference) {
const resolvedInputReference = await this.resolveRelayText(input_reference)
const response = await fetch(resolvedInputReference)
if (!response.ok) {
throw new AppException(ResponseCode.S3DownloadFileFailed)
}
inputReferenceUploadable = response
}
const result = await this.aiAvailability.executeAsync(
{ provider: 'openai', operation: 'videoGeneration', model: model || 'sora-2' },
() => this.openaiLibService.createVideo({
prompt,
input_reference: inputReferenceUploadable,
model: model as 'sora-2' | 'sora-2-pro',
// SDK 类型定义有误,实际支持 '10' | '15' | '25'
seconds: seconds as '4' | '8' | '12' | undefined,
size,
}),
r => r.id,
)
const aiLog = await this.aiLogRepo.create({View on GitHub (pinned to d3aa8bea5b)
Solutions
- Regenerate a fresh presigned/accessible URL for the reference image before calling createVideo (see toAccessibleUrl)
- Verify the image URL is publicly fetchable from the AI service (curl it from the host)
- Ensure RelayMediaResolverService is provided so relay/internal URLs are resolved before fetching
- Check bucket ACL/policy so the object is readable by the service
Example fix
// before input_reference: 'https://s3.aitoearn.cn/private/ref.png' // expired presigned URL // after input_reference: await this.toAccessibleUrl(assetPath) // fresh presigned URL
Defensive patterns
Strategy: validation
Validate before calling
const res = await fetch(inputReferenceUrl, { method: 'HEAD' })
if (!res.ok) throw new Error(`Reference image unreachable: HTTP ${res.status}`) Try / catch
try {
await openaiVideoService.createVideo({ input_reference: url, ... })
} catch (e) {
if (e instanceof AppException && e.code === ResponseCode.S3DownloadFileFailed) {
// refresh presigned URL and retry once
}
} Prevention
- Generate presigned URLs with sufficient expiry shortly before the call
- HEAD-check reference URLs before submitting the generation task
- Ensure the relay media resolver is wired so internal URLs are converted to fetchable ones
- Verify storage bucket read permissions from the AI service host
When it happens
Trigger: Calling createVideo with input_reference set to a URL whose fetch fails: presigned URL expired, private S3/OSS object not accessible, DNS failure, or relay media resolution returned an unreachable URL. Any non-2xx response.ok triggers the throw.
Common situations: Presigned URLs generated minutes earlier have expired by generation time; storage bucket permissions changed; relay media resolver disabled (@Optional dependency missing) so an internal/private URL is fetched directly; typo in stored asset path.
Related errors
- ResponseCode.S3DownloadFileFailed
- ResponseCode.S3DownloadFileFailed
- Failed to fetch image: ${response.status} ${response.statusT
- Download failed: ${response.status}
- Relay uploadSign returned no uploadUrl: ${JSON.stringify(sig
AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31).
Data as JSON: /api/errors/5c71544cea3dd82e.
Report an issue: GitHub.