{"record":{"id":"e8daf9e6973c4e1c","repo":"CherryHQ/cherry-studio","slug":"result-task-reason-task-failed","errorCode":null,"errorMessage":"result.task.reason || 'Task failed'","messagePattern":"result\\.task\\.reason \\|\\| 'Task failed'","errorType":"exception","errorClass":"PpioTaskFailedError","httpStatus":null,"severity":"error","filePath":"src/main/ai/provider/custom/ppio/ppioTransport.ts","lineNumber":436,"sourceCode":"    while (attempts < maxAttempts) {\n      if (signal?.aborted) {\n        throw createAbortError('Task polling aborted')\n      }\n\n      try {\n        const result = await this.getTaskResult(taskId, 10000, signal)\n        transientRetries = 0\n\n        if (result.task.progress_percent !== undefined && onProgress) {\n          onProgress(result.task.progress_percent)\n        }\n\n        if (result.task.status === 'TASK_STATUS_SUCCEED') {\n          return result\n        }\n\n        if (result.task.status === 'TASK_STATUS_FAILED') {\n          throw new PpioTaskFailedError(result.task.reason || 'Task failed')\n        }\n      } catch (error) {\n        if (signal?.aborted || (error instanceof Error && error.name === 'AbortError')) {\n          throw createAbortError('Task polling aborted')\n        }\n\n        // Terminal classifications — propagate without retrying. A 4xx (bar\n        // 429) poll response won't recover; 5xx / 429 fall through to the\n        // transient handling below (network blips, server hiccups, rate limits).\n        if (error instanceof PpioApiError && isTerminalHttpStatus(error.statusCode)) {\n          throw error\n        }\n\n        if (error instanceof PpioTaskFailedError) {\n          throw error\n        }\n\n        transientRetries++","sourceCodeStart":418,"sourceCodeEnd":454,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/provider/custom/ppio/ppioTransport.ts#L418-L454","documentation":"PpioTaskFailedError is the terminal failure for the PPIO async task lifecycle: getTaskResult returned task.status === 'TASK_STATUS_FAILED'. It is a distinct Error subclass so the poll loop can propagate it without retrying (previously it was misclassified as transient and silently retried 10×, burning credits). The message is the vendor's task.reason or 'Task failed'.","triggerScenarios":"PPIO's task-result endpoint returns TASK_STATUS_FAILED. Concrete causes: NSFW content detected, insufficient credits for the operation, content-policy violation, model-side generation error, or invalid input rejected at execution time.","commonSituations":"Prompt trips NSFW/policy filters, account out of credits mid-task, unsupported size/seed combination, or vendor model error.","solutions":["Read task.reason — it names the specific vendor cause (NSFW, insufficient credit, policy, etc.).","For NSFW/policy: adjust the prompt; for credits: top up and retry; for invalid params: correct size/seed.","Do NOT retry automatically — TASK_STATUS_FAILED is terminal and retrying burns credits. Surface to the user.","Catch PpioTaskFailedError specifically upstream and report as a user-facing task failure (not a transient retry)."],"exampleFix":"// before\ntry { await pollUntilDone(transport, taskId, ctx) } catch (e) { throw e }\n// after — distinguish terminal task failure from transient\ntry {\n  await pollUntilDone(transport, taskId, ctx)\n} catch (e) {\n  if (e instanceof PpioTaskFailedError) return { failed: true, reason: e.message }\n  throw e\n}","handlingStrategy":"try-catch","validationCode":"// Pre-validate prompt against obvious policy trip before submit (best-effort)\nif (/(<explicit terms>)/i.test(input.prompt ?? '')) {\n  throw new Error('Prompt may trip PPIO content policy')\n}","typeGuard":"export function isPpioTaskFailedError(e: unknown): e is Error {\n  return e instanceof Error && e.name === 'PpioTaskFailedError'\n}","tryCatchPattern":"try {\n  await pollUntilDone(transport, taskId, ctx)\n} catch (e) {\n  if (isPpioTaskFailedError(e)) {\n    return { failed: true, reason: e.message } // terminal, do NOT retry\n  }\n  throw e\n}","preventionTips":["Catch PpioTaskFailedError and treat as terminal — never auto-retry (it burns credits).","Read task.reason for the specific cause (NSFW, credit, policy).","Surface the reason to the user with a prompt-edit affordance."],"tags":["ppio","task-failed","terminal","image-generation","policy"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}