nexu-io/open-design · error · Error

leonardo.ai poll ${pollResp.status}

Error message

leonardo.ai poll ${pollResp.status}

What it means

Thrown inside the Leonardo polling loop when GET {baseUrl}/generations/{generationId} returns a non-2xx status. The message reports only the HTTP status code (no body). Distinct from [468] (FAILED status) and [469] (timeout): this fires on transport/auth-level rejection of the poll request itself.

Source

Thrown at apps/daemon/src/media/index.ts:2265

  }
  
  // Poll for completion
  const maxPollMs = 120000; // 2 minutes
  const pollIntervalMs = 2000; // 2 seconds
  const startedAt = Date.now();
  let imageUrl: string | null = null;
  
  while (Date.now() - startedAt < maxPollMs) {
    await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
    
    const pollResp = await fetch(`${baseUrl}/generations/${generationId}`, withMediaRequestInit(ctx, {
      headers: {
        'authorization': `Bearer ${credentials.apiKey}`,
      },
    }));
    
    if (!pollResp.ok) {
      throw new Error(`leonardo.ai poll ${pollResp.status}`);
    }
    
    const pollData = (await pollResp.json()) as Record<string, any>;
    const generation = pollData?.generations_by_pk;
    
    if (generation?.status === 'COMPLETE') {
      const images = generation?.generated_images;
      if (Array.isArray(images) && images.length > 0) {
        imageUrl = images[0]?.url;
        break;
      }
    } else if (generation?.status === 'FAILED') {
      throw new Error('leonardo.ai generation failed');
    }
  }
  
  if (!imageUrl) {
    throw new Error('leonardo.ai generation timed out after 2 minutes');

View on GitHub (pinned to 5be4028344)

Solutions

  1. Retry the entire generation — transient 5xx and 429 on polls usually resolve on a fresh submit.
  2. If 401/403: the key was likely invalidated mid-job; refresh LEONARDO_API_KEY and resubmit.
  3. If 404: the generationId is gone — the job was purged or never durably created; resubmit and confirm the submit step returned a real id.
  4. If 429 persists: increase pollIntervalMs (currently 2000ms) for your deployment to back off.

Example fix

// before — single poll attempt throws on any non-2xx
if (!pollResp.ok) {
  throw new Error(`leonardo.ai poll ${pollResp.status}`);
}

// after — tolerate transient 5xx/429 inside the poll window
if (!pollResp.ok) {
  if ((pollResp.status === 429 || pollResp.status >= 500) && Date.now() - startedAt < maxPollMs) {
    await new Promise(r => setTimeout(r, 5000));
    continue;
  }
  throw new Error(`leonardo.ai poll ${pollResp.status}`);
}
Defensive patterns

Strategy: retry

Try / catch

// Poll failures are dominated by transient 5xx/429; tolerate them inside the
// poll window, only throw on auth or definitive not-found.
while (Date.now() - startedAt < maxPollMs) {
  await new Promise(r => setTimeout(r, pollIntervalMs));
  let pollResp: Response;
  try {
    pollResp = await fetch(`${baseUrl}/generations/${generationId}`, withMediaRequestInit(ctx, { headers: { authorization: `Bearer ${credentials.apiKey}` } }));
  } catch (e) { throw new Error(`leonardo.ai poll network error: ${String(e)}`); }
  if (!pollResp.ok) {
    if ((pollResp.status === 429 || pollResp.status >= 500) && Date.now() - startedAt < maxPollMs) continue;
    throw new Error(`leonardo.ai poll ${pollResp.status}`);
  }
  // ... handle pollData
}

Prevention

When it happens

Trigger: Polling loop calls /generations/{id} and pollResp.ok is false — 401/403 (key lost scope mid-job), 404 (generationId doesn't exist or was purged), 429 (poll rate-limit), or 5xx (Leonardo outage).

Common situations: Polling too aggressively hitting Leonardo rate limits; generationId expired from Leonardo's retention window before completion; API key rotated mid-job; transient Leonardo 5xx during a poll window.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/b81ba9b0e68adb33. Report an issue: GitHub.