{"record":{"id":"25ba893bb845b599","repo":"CherryHQ/cherry-studio","slug":"image-generation-submit-for-sdkconfig-modelid","errorCode":null,"errorMessage":"Image generation submit for '${sdkConfig.modelId}' returned neither imageUrls nor a taskId","messagePattern":"Image generation submit for '(.+?)' returned neither imageUrls nor a taskId","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ai/provider/custom/tasks/imageGenerationJobHandler.ts","lineNumber":108,"sourceCode":"    const transport = resolveImageTransport(sdkConfig.providerId, sdkConfig.modelId, sdkConfig.providerSettings)\n    if (!transport) {\n      throw new Error(\n        `Image generation job: no async transport for '${sdkConfig.providerId}' (model '${sdkConfig.modelId}')`\n      )\n    }\n\n    // No persisted-task resume branch: `recovery: 'abandon'` means a job never\n    // outlives the process that enqueued it, so every execution starts at submit.\n    let urls: string[]\n    const submit = await transport.submit(await buildSubmitInput(input, sdkConfig.modelId, ctx.signal))\n    if (submit.imageUrls) {\n      urls = submit.imageUrls\n    } else if (submit.taskId) {\n      urls = await pollUntilDone(transport, submit.taskId, ctx)\n    } else {\n      // A malformed submit response (neither URLs nor a task id) must fail the\n      // job rather than silently complete with zero files (a paid no-op).\n      throw new Error(`Image generation submit for '${sdkConfig.modelId}' returned neither imageUrls nor a taskId`)\n    }\n\n    // Record before local download: the provider invocation completed even if file\n    // persistence fails. Polling is part of this invocation, not another billable\n    // call; a successful zero-image response is still an observable invocation.\n    if (captureContext) {\n      const completedAt = Date.now()\n      aiUsageRecordService.recordInvocation({\n        requestId: `custom-image:${ctx.jobId}`,\n        context: captureContext,\n        modality: 'image',\n        imageCount: urls.length,\n        metrics: { timeCompletionMs: Math.max(0, completedAt - usageStartedAt) },\n        completedAt\n      })\n    }\n\n    // An empty URL list from a *successful* submit/poll (e.g. content moderation","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/provider/custom/tasks/imageGenerationJobHandler.ts#L90-L126","documentation":"Error \"Image generation submit for '${sdkConfig.modelId}' returned neither imageUrls nor a taskId\" thrown in CherryHQ/cherry-studio.","triggerScenarios":"An async image generation transport's submit() resolves with neither imageUrls nor taskId — a malformed vendor response that the job handler cannot act on.","commonSituations":"Occurs when a custom image provider (DashScope, ModelScope, PPIO, etc.) returns a degraded or unexpected submit response, e.g. content moderation short-circuiting the task or a vendor API schema change. The job fails instead of silently completing with zero files (a paid no-op).","solutions":["Check the image transport implementation for the model: it must return either imageUrls for synchronous results or a taskId for async polling.","Verify the provider's image transport response parsing — some providers nest URLs under data[].url or output fields.","Enable debug logging for the provider transport to inspect the raw submit response and confirm which field carries the result."],"exampleFix":"In the provider's submit response parser, return { imageUrls } or { taskId }; e.g. return { taskId: data.task_id } when the API is asynchronous.","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"}