nexu-io/open-design · error · Error
leonardo.ai response missing generationId
Error message
leonardo.ai response missing generationId
What it means
Thrown when the Leonardo submit response parsed as JSON but the expected field submitData.sdGenerationJob.generationId is missing/falsy. Leonardo's documented contract is { sdGenerationJob: { generationId } }; a 2xx response without that path means the API shape changed or an unexpected success envelope was returned. The message is a fixed string (no interpolated values), so debugging requires capturing the raw response.
Source
Thrown at apps/daemon/src/media/index.ts:2246
},
body: JSON.stringify(body),
}));
const submitText = await submitResp.text();
if (!submitResp.ok) {
throw new Error(`leonardo.ai submit ${submitResp.status}: ${truncate(submitText, 240)}`);
}
let submitData: any;
try {
submitData = JSON.parse(submitText);
} catch {
throw new Error(`leonardo.ai non-JSON: ${truncate(submitText, 200)}`);
}
const generationId = submitData?.sdGenerationJob?.generationId;
if (!generationId) {
throw new Error('leonardo.ai response missing generationId');
}
// 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) {View on GitHub (pinned to 5be4028344)
Solutions
- Reproduce the call with curl against {baseUrl}/generations to inspect the actual response envelope and compare to Leonardo's current API docs.
- If Leonardo changed the shape, update the extraction path in apps/daemon/src/media/index.ts (line ~2246) to match the new contract.
- Confirm the configured baseUrl matches the Leonardo REST API version documented for your account; an older/newer version can return a different envelope.
- If the response is genuinely malformed, file a Leonardo support ticket with the request body.
Example fix
// before
const generationId = submitData?.sdGenerationJob?.generationId;
if (!generationId) {
throw new Error('leonardo.ai response missing generationId');
}
// after — surface body for diagnosis + accept alternate shapes
const generationId =
submitData?.sdGenerationJob?.generationId
?? submitData?.generationId
?? submitData?.id;
if (!generationId) {
throw new Error(`leonardo.ai response missing generationId: ${truncate(JSON.stringify(submitData), 240)}`);
} Defensive patterns
Strategy: type-guard
Type guard
// Narrow the parsed Leonardo submit response so missing generationId is caught
// at the boundary with a typed error instead of a stringy runtime throw.
interface LeonardoSubmitOk { sdGenerationJob: { generationId: string } }
function isLeonardoSubmitOk(d: unknown): d is LeonardoSubmitOk {
return !!d
&& typeof d === 'object'
&& typeof (d as any)?.sdGenerationJob?.generationId === 'string'
&& (d as any).sdGenerationJob.generationId.length > 0;
} Try / catch
let submitData: any;
try { submitData = JSON.parse(submitText); } catch { throw new Error(`leonardo.ai non-JSON: ${truncate(submitText, 200)}`); }
if (!isLeonardoSubmitOk(submitData)) {
// Surface the body so the next dev can see what shape Leonardo actually returned.
throw new Error(`leonardo.ai response missing generationId: ${truncate(JSON.stringify(submitData), 240)}`);
}
const generationId = submitData.sdGenerationJob.generationId; Prevention
- Pin the Leonardo REST API version in baseUrl and document which version the parser was written against.
- When Leonardo ships API changes, update the type guard and parser together; never let one drift.
- Log the raw submit body on shape mismatch (truncated) so future regressions are debuggable from one occurrence.
When it happens
Trigger: Leonardo returns 2xx JSON without sdGenerationJob.generationId — e.g. a different envelope ({ generationId } at root, or { sdGenerationJob: { generationId: null } }), or an empty success object. The current code can't surface the body in the message.
Common situations: Leonardo ships an API version bump that changes the response shape; the configured baseUrl points at a different Leonardo API version; Leonardo returns a soft-success that is actually an error (e.g. validation passed-through as 200).
Related errors
- grok video submit returned no inline video and no request_id
- registerLibraryAsset requires bytes, text, or absPath
- openai non-JSON response: ${truncate(text, 200)}
- openai response had no data[0]
- openai response had neither b64_json nor url
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/b452bb68f3ec35dc.
Report an issue: GitHub.