{"record":{"id":"c7ee969409191f50","repo":"CherryHQ/cherry-studio","slug":"image-generation-produced-urls-length-url-s-bu","errorCode":null,"errorMessage":"Image generation produced ${urls.length} URL(s) but all downloads failed","messagePattern":"Image generation produced (.+?) URL\\(s\\) but all downloads failed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ai/provider/custom/tasks/imageGenerationJobHandler.ts","lineNumber":224,"sourceCode":"  const fileManager = application.get('FileManager')\n  const files: FileEntry[] = []\n  for (const url of urls) {\n    if (signal.aborted) throw createAbortError('Image generation aborted')\n    const downloaded = await downloadImageAsBase64(url)\n    if (!downloaded) continue\n    files.push(\n      await fileManager.createInternalEntry({\n        source: 'base64',\n        data: `data:${downloaded.media_type || 'image/png'};base64,${downloaded.data}`,\n        cleanupPolicy\n      })\n    )\n  }\n  // The remote generation succeeded (it returned URLs); surfacing a hard failure\n  // when none could be downloaded avoids reporting a paid generation as an empty,\n  // silent success. A partial failure still returns what we have, with a warning.\n  if (files.length === 0) {\n    throw new Error(`Image generation produced ${urls.length} URL(s) but all downloads failed`)\n  }\n  if (files.length < urls.length) {\n    logger.warn('Some generated image downloads failed', { requested: urls.length, persisted: files.length })\n  }\n  return files\n}\n\n/**\n * Best-effort delete of temp image-input `file_entry` copies. Called by AiService\n * to clean up inputs it created when the job enqueue fails before the job owns\n * them. Once a job is enqueued its inputs are held by `job_file_ref`, and the\n * cleanup pass reclaims them when the job row is pruned — there is no ad-hoc\n * post-job delete (file-entry-cleanup.md §4.1/§5.1). Idempotent and non-throwing.\n */\nexport async function deleteImageInputEntries(ids: ReadonlyArray<string | undefined>): Promise<void> {\n  const present = ids.filter((id): id is string => Boolean(id))\n  if (present.length === 0) return\n  const fileManager = application.get('FileManager')","sourceCodeStart":206,"sourceCodeEnd":242,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/provider/custom/tasks/imageGenerationJobHandler.ts#L206-L242","documentation":"Error \"Image generation produced ${urls.length} URL(s) but all downloads failed\" thrown in CherryHQ/cherry-studio.","triggerScenarios":"The remote generation succeeded and returned one or more image URLs, but every downloadAndPersistImageUrls attempt failed, leaving zero persisted files.","commonSituations":"Caused by expired/signed URL expiry, network failures, or blocked CDN hosts when fetching generated images. A hard failure is surfaced so a paid generation is not reported as an empty silent success; partial failures still return the files that downloaded, with a warning.","solutions":["Check network connectivity to the CDN hosts serving the image URLs and retry.","Verify the URLs have not expired — some providers sign URLs with short TTLs; download immediately after generation.","Inspect per-download errors in the job logs to see whether failures are 403/expired-signature or timeouts."],"exampleFix":"Download images as soon as the task completes, and on failure surface the underlying HTTP status: fetch(url) then check response.ok before reading the blob.","handlingStrategy":null,"validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}