nexu-io/open-design · error · Error
leonardo.ai generation timed out after 2 minutes
Error message
leonardo.ai generation timed out after 2 minutes
What it means
Thrown after the Leonardo polling loop exits (startedAt + maxPollMs = 120000ms = 2 minutes) without imageUrl being set. This is the poll-ceiling timeout: the job neither reached COMPLETE nor FAILED inside the 2-minute window. The message hard-codes '2 minutes' because maxPollMs is a constant.
Source
Thrown at apps/daemon/src/media/index.ts:2283
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');
}
// Fetch the generated image
const imgResp = await fetch(imageUrl, withMediaRequestInit(ctx));
if (!imgResp.ok) {
throw new Error(`leonardo.ai image fetch ${imgResp.status}`);
}
const bytes = Buffer.from(await imgResp.arrayBuffer());
return {
bytes,
providerNote: `leonardo.ai/${ctx.model} · ${ctx.aspect} · ${bytes.length} bytes`,
suggestedExt: sniffImageExt(bytes),
};
}
View on GitHub (pinned to 5be4028344)
Solutions
- Retry — slow queue days often resolve on resubmit.
- Reduce requested complexity (smaller dimensions like 1:1 1024², simpler prompt) so the job finishes inside 2 minutes.
- If your jobs consistently need longer, raise the maxPollMs constant (currently 120000) in apps/daemon/src/media/index.ts around line 2250 — consider making it env-configurable like the OpenRouter/Grok ceilings.
- If status was returning COMPLETE but imageUrl stayed null (images array empty), that's a different bug — capture pollData to inspect generations_by_pk.generated_images.
Example fix
// before const maxPollMs = 120000; // 2 minutes // after — env-configurable ceiling matching the OpenRouter/Grok pattern const maxPollMs = Number(process.env.OD_LEONARDO_MAX_POLL_MS) || 120000;
Defensive patterns
Strategy: validation
Validate before calling
// Make the Leonardo poll ceiling configurable like OpenRouter/Grok so long jobs // don't always time out at the 2-minute hard cap. const maxPollMs = Number(process.env.OD_LEONARDO_MAX_POLL_MS) || 120000;
Try / catch
// Distinguish three post-loop states: status stuck on PENDING (queue backlog),
// status stuck on a value we don't recognize (parser drift), or COMPLETE with
// no images (upstream bug). Each needs a different operator action.
if (!imageUrl) {
if (lastStatus === 'COMPLETE') throw new Error('leonardo.ai COMPLETE but no images — upstream bug, file a ticket');
throw new Error(`leonardo.ai generation timed out after 2 minutes (last status: ${lastStatus || 'pending'})`);
} Prevention
- Make the Leonardo ceiling env-configurable (OD_LEONARDO_MAX_POLL_MS) to match the pattern already used for OpenRouter and Grok.
- Track lastStatus at timeout — 'PENDING' means queue, anything else likely means parser drift on a new status string.
- Avoid hardcoded ceilings in new providers; reach for env-config from the start.
When it happens
Trigger: Loop iterates every 2000ms for at most 120000ms; if generation.status never becomes 'COMPLETE' (and never 'FAILED'), imageUrl stays null and the post-loop guard throws. Happens on slow Leonardo jobs that take >2min or when status gets stuck on PENDING.
Common situations: Busy Leonardo queue making jobs run >2min; complex prompt or large size that legitimately needs >2min; status stuck because the poll response shape changed and the COMPLETE branch never matches; maxPollMs is a constant so long jobs always time out.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- openrouter video timed out after ${elapsedSec}s waiting for
- no Leonardo.ai API key — configure it in Settings or set LEO
- unsupported leonardo.ai model: ${ctx.model}
- leonardo.ai non-JSON: ${truncate(submitText, 200)}
- grok video timed out after ${elapsedSec}s waiting for status
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/8334ecd8508fa62e.
Report an issue: GitHub.