CherryHQ/cherry-studio · error · Error

PPIO API request timeout after ${timeout / 1000}s

Error message

PPIO API request timeout after ${timeout / 1000}s

What it means

Thrown when PpioTransport.request()'s internal timeout AbortController fires before the fetch resolves, and the abort did NOT come from the caller's external signal (the external-abort path throws a synthesized AbortError instead). Reports the configured timeout in seconds.

Source

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

    }

    try {
      const response = await fetch(url, fetchOptions)

      if (!response.ok) {
        const errorText = (await response.text().catch(() => '')).slice(0, 500)
        throw new PpioApiError(`PPIO API error: ${response.status} - ${errorText}`, response.status)
      }

      const data = await response.json()
      return data as T
    } catch (error) {
      if (error instanceof Error && error.name === 'AbortError') {
        if (externallyAborted) {
          throw createAbortError('PPIO API request aborted')
        }

        throw new Error(`PPIO API request timeout after ${timeout / 1000}s`)
      }
      throw error
    } finally {
      clearTimeout(timeoutId)
      externalSignal?.removeEventListener('abort', onExternalAbort)
    }
  }

  async submit(input: ImageGenerationSubmitInput): Promise<{ taskId?: string; imageUrls?: string[] }> {
    const bagParams = input.providerParams as PpioProviderParams
    const descriptor = input.modelDescriptor
    if (!descriptor) {
      throw new Error(`Unknown model: ${input.modelId}`)
    }

    // Native AI SDK fields (size / seed) land on `input.*` post-canonicalGenerate
    // partition, not in the providerOptions bag. Merge them into a unified
    // view so the per-model builders below can read uniformly. `ppioSeed`

View on GitHub (pinned to 726446b54c)

Solutions

  1. Retry once with backoff — most PPIO timeouts are transient.
  2. Raise options.timeout for submit (120000ms) if the vendor is known slow; raise poll timeout (10000ms) if getTaskResult consistently stalls.
  3. Verify network/proxy reachability to api.ppio.com; check for corporate proxies that drop long POSTs.
  4. Confirm the user is not on a degraded connection.

Example fix

// before
const r = await this.request(endpoint, body, 'POST', { timeout: 120000 })
// after — escalate for slow vendors
const r = await this.request(endpoint, body, 'POST', { timeout: slowVendor ? 300000 : 120000 })
Defensive patterns

Strategy: retry

Validate before calling

// No request-time validation prevents a timeout; budget it
const timeout = slowVendor ? 300000 : DEFAULT_TIMEOUT

Type guard

export function isPpioTimeoutError(e: unknown): boolean {
  return e instanceof Error && /^PPIO API request timeout after/.test(e.message)
}

Try / catch

try {
  await transport.submit(input)
} catch (e) {
  if (isPpioTimeoutError(e)) await backoffRetry(() => transport.submit(input), { maxAttempts: 2 })
  throw e
}

Prevention

When it happens

Trigger: request() with a `timeout` (DEFAULT_TIMEOUT default, 120000ms for submit, 10000ms for poll getTaskResult) where fetch neither resolves nor is externally aborted in time. Happens during vendor slowness, cold model load, network stalls, or proxy buffering.

Common situations: Submit exceeding the 120s budget on a slow link, poll GET exceeding 10s during vendor overload, proxy dropping long-lived connections, or offline/metered network.

Understand the failure class

Related errors


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