decolua/9router · error
NanoBanana polling timeout
Error message
NanoBanana polling timeout
What it means
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.
Source
Thrown at open-sse/handlers/imageProviders/nanobanana.js:53
},
// Async: parse submit → poll until SUCCESS, return raw poll data
async parseResponse(response, { headers }) {
const submitData = await response.json();
if (submitData.code !== 200) throw new Error(submitData.msg || "NanoBanana submit failed");
const taskId = submitData.data?.taskId;
if (!taskId) throw new Error("NanoBanana: no taskId returned");
const pollUrl = `${POLL_BASE}?taskId=${encodeURIComponent(taskId)}`;
const deadline = Date.now() + POLL_TIMEOUT_MS;
while (Date.now() < deadline) {
await sleep(POLL_INTERVAL_MS);
const r = await fetch(pollUrl, { headers });
if (!r.ok) throw new Error(`NanoBanana status ${r.status}`);
const s = await r.json();
const flag = s.data?.successFlag;
if (flag === 1) return s.data;
if (flag === 2 || flag === 3) throw new Error(s.data?.errorMessage || "NanoBanana generation failed");
}
throw new Error("NanoBanana polling timeout");
},
normalize: (responseBody, prompt) => {
const url = responseBody.response?.resultImageUrl || responseBody.response?.originImageUrl;
if (url) return { created: nowSec(), data: [{ url, revised_prompt: prompt }] };
return { created: nowSec(), data: [] };
},
};
View on GitHub (pinned to 90b52e06ff)
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.
Defensive patterns
Strategy: validation
Validate before calling
// Validate poll config and timeout budget before submitting an async task
const cfg = PROVIDER_MEDIA["nanobanana"]?.imageConfig;
if (!cfg?.pollUrl) throw new Error("NanoBanana pollUrl not configured");
import { POLL_TIMEOUT_MS } from "./_base.js";
if (POLL_TIMEOUT_MS < 60_000) console.warn("NanoBanana POLL_TIMEOUT_MS is low for large generations"); Type guard
function hasTerminalStatus(s) {
const flag = s?.data?.successFlag;
return flag === 1 || flag === 2 || flag === 3; // only these end the loop; anything else = still pending
} Try / catch
try {
const data = await imageProvider.parseResponse(response, { headers });
} catch (e) {
if (e.message === "NanoBanana polling timeout") {
// Task may still complete upstream — inform the caller rather than reporting a hard failure
return { error: { code: "poll_timeout", retryable: true, detail: "task still pending after timeout" } };
}
throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Runway: no task id returned
- Runway polling timeout
- BFL: no polling_url returned
- NanoBanana status ${r.status}
- NanoBanana generation failed
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/b120dfd11b7a1881.
Report an issue: GitHub.