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

  1. Read the truncated body in the error — HTML/doctype tags confirm a misrouted or intercepted response.
  2. Verify credentials.baseUrl (or the default https://generativelanguage.googleapis.com) is correct and reachable as JSON.
  3. Disable any HTTP proxy that may be rewriting responses, or whitelist the Google API host.
  4. 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

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


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