CherryHQ/cherry-studio · error · PpioTaskFailedError

result.task.reason || 'Task failed'

Error message

result.task.reason || 'Task failed'

What it means

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'.

Source

Thrown at src/main/ai/provider/custom/ppio/ppioTransport.ts:436

    while (attempts < maxAttempts) {
      if (signal?.aborted) {
        throw createAbortError('Task polling aborted')
      }

      try {
        const result = await this.getTaskResult(taskId, 10000, signal)
        transientRetries = 0

        if (result.task.progress_percent !== undefined && onProgress) {
          onProgress(result.task.progress_percent)
        }

        if (result.task.status === 'TASK_STATUS_SUCCEED') {
          return result
        }

        if (result.task.status === 'TASK_STATUS_FAILED') {
          throw new PpioTaskFailedError(result.task.reason || 'Task failed')
        }
      } catch (error) {
        if (signal?.aborted || (error instanceof Error && error.name === 'AbortError')) {
          throw createAbortError('Task polling aborted')
        }

        // Terminal classifications — propagate without retrying. A 4xx (bar
        // 429) poll response won't recover; 5xx / 429 fall through to the
        // transient handling below (network blips, server hiccups, rate limits).
        if (error instanceof PpioApiError && isTerminalHttpStatus(error.statusCode)) {
          throw error
        }

        if (error instanceof PpioTaskFailedError) {
          throw error
        }

        transientRetries++

View on GitHub (pinned to 726446b54c)

Solutions

  1. Read task.reason — it names the specific vendor cause (NSFW, insufficient credit, policy, etc.).
  2. For NSFW/policy: adjust the prompt; for credits: top up and retry; for invalid params: correct size/seed.
  3. Do NOT retry automatically — TASK_STATUS_FAILED is terminal and retrying burns credits. Surface to the user.
  4. Catch PpioTaskFailedError specifically upstream and report as a user-facing task failure (not a transient retry).

Example fix

// before
try { await pollUntilDone(transport, taskId, ctx) } catch (e) { throw e }
// after — distinguish terminal task failure from transient
try {
  await pollUntilDone(transport, taskId, ctx)
} catch (e) {
  if (e instanceof PpioTaskFailedError) return { failed: true, reason: e.message }
  throw e
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-validate prompt against obvious policy trip before submit (best-effort)
if (/(<explicit terms>)/i.test(input.prompt ?? '')) {
  throw new Error('Prompt may trip PPIO content policy')
}

Type guard

export function isPpioTaskFailedError(e: unknown): e is Error {
  return e instanceof Error && e.name === 'PpioTaskFailedError'
}

Try / catch

try {
  await pollUntilDone(transport, taskId, ctx)
} catch (e) {
  if (isPpioTaskFailedError(e)) {
    return { failed: true, reason: e.message } // terminal, do NOT retry
  }
  throw e
}

Prevention

When it happens

Trigger: 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.

Common situations: Prompt trips NSFW/policy filters, account out of credits mid-task, unsupported size/seed combination, or vendor model error.

Related errors


AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12). Data as JSON: /api/errors/e8daf9e6973c4e1c. Report an issue: GitHub.