{"record":{"id":"b120dfd11b7a1881","repo":"decolua/9router","slug":"nanobanana-polling-timeout","errorCode":null,"errorMessage":"NanoBanana polling timeout","messagePattern":"NanoBanana polling timeout","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"open-sse/handlers/imageProviders/nanobanana.js","lineNumber":53,"sourceCode":"  },\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":35,"sourceCodeEnd":61,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/open-sse/handlers/imageProviders/nanobanana.js#L35-L61","documentation":"Thrown when the NanoBanana task never reaches a terminal state within POLL_TIMEOUT_MS: the polling loop exits and this error is raised unconditionally at nanobanana.js:53. The task was submitted successfully and every poll returned 200, but successFlag stayed 0 (pending/running) until the deadline.","triggerScenarios":"The while (Date.now() < deadline) loop in open-sse/handlers/imageProviders/nanobanana.js:44-52 expires with `s.data?.successFlag` never equal to 1, 2, or 3 — e.g. the provider is still queued after POLL_TIMEOUT_MS, or returns an unexpected shape (s.data missing / successFlag absent) forever.","commonSituations":"Very large numImages or high upstream queue load exceeding the fixed POLL_TIMEOUT_MS from _base.js; the callBackUrl dummy value causing upstream to stall; provider schema change so successFlag is no longer under s.data (poll looks pending forever); wrong pollUrl returning 200 with a non-status body.","solutions":["Increase POLL_TIMEOUT_MS (or pass a larger per-provider timeout) if generations are simply slow — check typical latency for your model/numImages.","Log the last poll payload before the throw to confirm the response shape still contains data.successFlag; adjust parsing if the provider changed its schema.","Verify POLL_BASE/imageConfig.pollUrl points at the real record-info endpoint (a wrong endpoint can 200 forever without status).","Reduce numImages (body.n) or prompt complexity to shorten generation time and retry.","As a hardening step, treat a very old taskId as failed: re-submit if polling exceeds, say, 2x your historical p99 duration."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Validate poll config and timeout budget before submitting an async task\nconst cfg = PROVIDER_MEDIA[\"nanobanana\"]?.imageConfig;\nif (!cfg?.pollUrl) throw new Error(\"NanoBanana pollUrl not configured\");\nimport { POLL_TIMEOUT_MS } from \"./_base.js\";\nif (POLL_TIMEOUT_MS < 60_000) console.warn(\"NanoBanana POLL_TIMEOUT_MS is low for large generations\");","typeGuard":"function hasTerminalStatus(s) {\n  const flag = s?.data?.successFlag;\n  return flag === 1 || flag === 2 || flag === 3; // only these end the loop; anything else = still pending\n}","tryCatchPattern":"try {\n  const data = await imageProvider.parseResponse(response, { headers });\n} catch (e) {\n  if (e.message === \"NanoBanana polling timeout\") {\n    // Task may still complete upstream — inform the caller rather than reporting a hard failure\n    return { error: { code: \"poll_timeout\", retryable: true, detail: \"task still pending after timeout\" } };\n  }\n  throw e;\n}","preventionTips":["Size POLL_TIMEOUT_MS to your worst-case generation time (large numImages, peak-hours queues).","Log the final poll payload before timeout to detect provider schema drift (successFlag missing/relocated).","Prefer smaller numImages for interactive requests.","Periodically verify the poll endpoint still returns data.successFlag."],"tags":["timeout","async-polling","image-generation","schema-change"],"backgroundTag":"async-task-polling-timeout","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}