{"record":{"id":"4a662a6bc3f20c91","repo":"decolua/9router","slug":"nanobanana-generation-failed","errorCode":null,"errorMessage":"NanoBanana generation failed","messagePattern":"NanoBanana generation failed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"open-sse/handlers/imageProviders/nanobanana.js","lineNumber":51,"sourceCode":"    }\n    return req;\n  },\n  // Async: parse submit → poll until SUCCESS, return raw poll data\n  async parseResponse(response, { headers }) {\n    const submitData = await response.json();\n    if (submitData.code !== 200) throw new Error(submitData.msg || \"NanoBanana submit failed\");\n    const taskId = submitData.data?.taskId;\n    if (!taskId) throw new Error(\"NanoBanana: no taskId returned\");\n    const pollUrl = `${POLL_BASE}?taskId=${encodeURIComponent(taskId)}`;\n    const deadline = Date.now() + POLL_TIMEOUT_MS;\n    while (Date.now() < deadline) {\n      await sleep(POLL_INTERVAL_MS);\n      const r = await fetch(pollUrl, { headers });\n      if (!r.ok) throw new Error(`NanoBanana status ${r.status}`);\n      const s = await r.json();\n      const flag = s.data?.successFlag;\n      if (flag === 1) return s.data;\n      if (flag === 2 || flag === 3) throw new Error(s.data?.errorMessage || \"NanoBanana generation failed\");\n    }\n    throw new Error(\"NanoBanana polling timeout\");\n  },\n  normalize: (responseBody, prompt) => {\n    const url = responseBody.response?.resultImageUrl || responseBody.response?.originImageUrl;\n    if (url) return { created: nowSec(), data: [{ url, revised_prompt: prompt }] };\n    return { created: nowSec(), data: [] };\n  },\n};\n","sourceCodeStart":33,"sourceCodeEnd":61,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/open-sse/handlers/imageProviders/nanobanana.js#L33-L61","documentation":"Thrown when the NanoBanana poll endpoint reports the generation task itself failed: the successFlag field comes back 2 or 3 (failure codes). The error message is the provider's own s.data.errorMessage when present, falling back to this generic string. Unlike [50], the HTTP call succeeded — the upstream task genuinely failed.","triggerScenarios":"During polling in open-sse/handlers/imageProviders/nanobanana.js:49-51, the poll JSON satisfies `s.data?.successFlag === 2 || s.data?.successFlag === 3`. The thrown message equals `s.data?.errorMessage` when provided, otherwise the literal 'NanoBanana generation failed'.","commonSituations":"Prompt rejected by upstream content moderation; image edit requests (type IMAGETOIAMGE) with unreachable or rejected imageUrls; invalid numImages/ratio combos; upstream GPU capacity failures; generic 'generation failed' because the provider returns successFlag 2/3 with no errorMessage.","solutions":["Log/inspect the full poll response to read s.data.errorMessage — the thrown Error already carries it when the provider supplies one.","If the message is the generic fallback, re-run with a simpler/safer prompt to rule out moderation rejection.","For edit requests, verify every URL in imageUrls/body.image is publicly fetchable and a supported format (png/jpeg).","Retry once with a fresh submit — transient upstream capacity failures are common with this async API.","Check numImages (body.n) and image_size/ratio values against the provider's supported ranges."],"exampleFix":"// before (caller has no provider detail when errorMessage is absent)\nconst flag = s.data?.successFlag;\nif (flag === 2 || flag === 3) throw new Error(s.data?.errorMessage || \"NanoBanana generation failed\");\n// after\nconst flag = s.data?.successFlag;\nif (flag === 2 || flag === 3) throw new Error(s.data?.errorMessage || `NanoBanana generation failed (successFlag=${flag}, taskId=${taskId})`);","handlingStrategy":"try-catch","validationCode":"// Validate the request before submitting to reduce upstream task failures\nif (!body.prompt || !body.prompt.trim()) throw new Error(\"prompt is required\");\nconst urls = [body.image, ...(Array.isArray(body.images) ? body.images : [])].filter(Boolean);\nfor (const u of urls) {\n  if (!/^https?:\\/\\//.test(u)) throw new Error(`image URL must be http(s): ${u}`);\n}","typeGuard":"function isPollResult(s) { return !!s && typeof s === \"object\" && s.data != null && typeof s.data.successFlag === \"number\"; }\n// then: if (isPollResult(s) && (s.data.successFlag === 2 || s.data.successFlag === 3)) -> failed","tryCatchPattern":"try {\n  const data = await imageProvider.parseResponse(response, { headers });\n} catch (e) {\n  if (e.message === \"NanoBanana generation failed\" || /NanoBanana/.test(e.message)) {\n    console.error(\"NanoBanana task failed:\", e.message); // e.message may carry provider errorMessage\n    return { error: { code: \"generation_failed\", detail: e.message } }; // surface to client instead of crashing\n  }\n  throw e;\n}","preventionTips":["Pre-screen prompts for likely moderation triggers before submitting.","For edit (image-to-image) calls, verify source image URLs are public and fetchable.","Keep numImages and ratio within provider-supported ranges.","Log full poll payloads so successFlag 2/3 failures can be diagnosed from provider errorMessage."],"tags":["upstream-failure","async-polling","image-generation","content-moderation"],"backgroundTag":"upstream-task-failed","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}