nexu-io/open-design · error · Error
nano-banana image non-JSON: ${truncate(text, 200)}
Error message
nano-banana image non-JSON: ${truncate(text, 200)} What it means
Thrown by renderNanoBananaImage when the upstream returned HTTP 2xx but the body failed JSON.parse — i.e. the endpoint returned HTML/plaintext (a captive portal, an error page, or a misrouted response) where JSON was expected. It guards against silently passing garbage into the field-extraction step.
Source
Thrown at apps/daemon/src/media/index.ts:1714
() => fetch(`${baseUrl}/v1beta/models/${encodeURIComponent(wireModel)}:generateContent`, withMediaRequestInit(ctx, {
method: 'POST',
headers: nanoBananaHeaders(baseUrl, apiKey),
body: JSON.stringify(body),
})),
(summary) => ctx.onProviderRequestSettled?.({
providerId: 'nanobanana',
...summary,
}),
);
const text = await resp.text();
if (!resp.ok) {
throw new Error(`nano-banana image ${resp.status}: ${truncate(text, 240)}`);
}
let data: any;
try {
data = JSON.parse(text);
} catch {
throw new Error(`nano-banana image non-JSON: ${truncate(text, 200)}`);
}
const bytes = inlineImageBytesFromGenerateContent(data);
return {
bytes,
providerNote: `nano-banana/${wireModel} · ${nanoBananaAspectFor(ctx.aspect)} · ${NANOBANANA_DEFAULT_IMAGE_SIZE} · ${bytes.length} bytes`,
suggestedExt: sniffImageExt(bytes),
};
}
function nanoBananaHeaders(baseUrl: string, apiKey: string): Record<string, string> {
const headers: Record<string, string> = {
'content-type': 'application/json',
};
if (usesOfficialGoogleApiKeyHeader(baseUrl)) {
headers['x-goog-api-key'] = apiKey;
return headers;
}
headers.authorization = `Bearer ${apiKey}`;View on GitHub (pinned to 5be4028344)
Solutions
- Read the truncated body in the error — HTML/doctype tags confirm a misrouted or intercepted response.
- Verify credentials.baseUrl (or the default https://generativelanguage.googleapis.com) is correct and reachable as JSON.
- Disable any HTTP proxy that may be rewriting responses, or whitelist the Google API host.
- Retry once; transient interstitials during network handoffs do occur.
Example fix
// before
try { data = JSON.parse(text); }
catch { throw new Error(`nano-banana image non-JSON: ${truncate(text, 200)}`); }
// after — distinguish HTML interception from malformed JSON
try { data = JSON.parse(text); }
catch (e) {
const looksLikeHtml = /<html|<!doctype/i.test(text);
throw new Error(
`nano-banana image ${looksLikeHtml ? 'returned HTML (proxy/interception?)' : 'non-JSON'}: ${truncate(text, 200)}`,
);
} Defensive patterns
Strategy: validation
Validate before calling
// Detect HTML/non-JSON interception before parsing
function looksLikeHtmlBody(text: string): boolean {
return /<html|<!doctype|<title>/i.test(text.slice(0, 200));
}
if (looksLikeHtmlBody(text)) {
throw new Error(`nano-banana image returned HTML (proxy/interception or wrong baseUrl): ${truncate(text, 200)}`);
} Type guard
function isJsonContentType(resp: Response): boolean {
const ct = resp.headers.get('content-type') || '';
return ct.includes('application/json') || ct.includes('+json');
} Try / catch
let data: any;
try {
data = JSON.parse(text);
} catch (e) {
if (looksLikeHtmlBody(text)) {
throw new Error(`nano-banana image returned HTML (interception?): ${truncate(text, 200)}`);
}
throw new Error(`nano-banana image non-JSON (${String(e).slice(0, 80)}): ${truncate(text, 200)}`);
} Prevention
- Check content-type header before parsing; treat text/html as an interception signal.
- Validate baseUrl points at a Google API host, not a marketing/parked page.
- Bypass HTTP proxies for the Google API host in CI environments.
- Log the first 200 chars of any non-JSON body for diagnosis.
When it happens
Trigger: A proxy/CDN returns an HTML error page with 200 status, credentials.baseUrl points at a host that returns non-JSON (e.g. a marketing page), or the connection was hijacked by a captive portal returning an auth page.
Common situations: Misconfigured baseUrl (typo'd domain hitting a parked page), corporate proxy returning an HTML block page, or a regional Google endpoint that returns an HTML interstitial during outages.
Related errors
- nano-banana image response missing candidates[].content.part
- no Nano Banana API key — configure it in Settings or set OD_
- nano-banana image ${resp.status}: ${truncate(text, 240)}
- openrouter image non-JSON response: ${truncate(text, 200)}
- aihubmix image (gemini) ${resp.status}: ${text.slice(0, 240)
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/6663ba76f494d6a8.
Report an issue: GitHub.