{"record":{"id":"6663ba76f494d6a8","repo":"nexu-io/open-design","slug":"nano-banana-image-non-json-truncate-text-200","errorCode":null,"errorMessage":"nano-banana image non-JSON: ${truncate(text, 200)}","messagePattern":"nano-banana image non-JSON: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/daemon/src/media/index.ts","lineNumber":1714,"sourceCode":"    () => fetch(`${baseUrl}/v1beta/models/${encodeURIComponent(wireModel)}:generateContent`, withMediaRequestInit(ctx, {\n      method: 'POST',\n      headers: nanoBananaHeaders(baseUrl, apiKey),\n      body: JSON.stringify(body),\n    })),\n    (summary) => ctx.onProviderRequestSettled?.({\n      providerId: 'nanobanana',\n      ...summary,\n    }),\n  );\n  const text = await resp.text();\n  if (!resp.ok) {\n    throw new Error(`nano-banana image ${resp.status}: ${truncate(text, 240)}`);\n  }\n  let data: any;\n  try {\n    data = JSON.parse(text);\n  } catch {\n    throw new Error(`nano-banana image non-JSON: ${truncate(text, 200)}`);\n  }\n  const bytes = inlineImageBytesFromGenerateContent(data);\n  return {\n    bytes,\n    providerNote: `nano-banana/${wireModel} · ${nanoBananaAspectFor(ctx.aspect)} · ${NANOBANANA_DEFAULT_IMAGE_SIZE} · ${bytes.length} bytes`,\n    suggestedExt: sniffImageExt(bytes),\n  };\n}\n\nfunction nanoBananaHeaders(baseUrl: string, apiKey: string): Record<string, string> {\n  const headers: Record<string, string> = {\n    'content-type': 'application/json',\n  };\n  if (usesOfficialGoogleApiKeyHeader(baseUrl)) {\n    headers['x-goog-api-key'] = apiKey;\n    return headers;\n  }\n  headers.authorization = `Bearer ${apiKey}`;","sourceCodeStart":1696,"sourceCodeEnd":1732,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/media/index.ts#L1696-L1732","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\ntry { data = JSON.parse(text); }\ncatch { throw new Error(`nano-banana image non-JSON: ${truncate(text, 200)}`); }\n\n// after — distinguish HTML interception from malformed JSON\ntry { data = JSON.parse(text); }\ncatch (e) {\n  const looksLikeHtml = /<html|<!doctype/i.test(text);\n  throw new Error(\n    `nano-banana image ${looksLikeHtml ? 'returned HTML (proxy/interception?)' : 'non-JSON'}: ${truncate(text, 200)}`,\n  );\n}","handlingStrategy":"validation","validationCode":"// Detect HTML/non-JSON interception before parsing\nfunction looksLikeHtmlBody(text: string): boolean {\n  return /<html|<!doctype|<title>/i.test(text.slice(0, 200));\n}\n\nif (looksLikeHtmlBody(text)) {\n  throw new Error(`nano-banana image returned HTML (proxy/interception or wrong baseUrl): ${truncate(text, 200)}`);\n}","typeGuard":"function isJsonContentType(resp: Response): boolean {\n  const ct = resp.headers.get('content-type') || '';\n  return ct.includes('application/json') || ct.includes('+json');\n}","tryCatchPattern":"let data: any;\ntry {\n  data = JSON.parse(text);\n} catch (e) {\n  if (looksLikeHtmlBody(text)) {\n    throw new Error(`nano-banana image returned HTML (interception?): ${truncate(text, 200)}`);\n  }\n  throw new Error(`nano-banana image non-JSON (${String(e).slice(0, 80)}): ${truncate(text, 200)}`);\n}","preventionTips":["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."],"tags":["nano-banana","gemini","image-generation","response-shape","network","provider-api"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}