decolua/9router · error
NanoBanana status ${r.status}
Error message
NanoBanana status ${r.status} What it means
Thrown by NanoBanana's parseResponse while polling the async generation task endpoint. Every poll fetch is checked with response.ok, and any non-2xx poll response (4xx/5xx) aborts the whole image generation immediately — there is no retry on transient poll failures. It means the poll HTTP call itself failed, not that the generation failed.
Source
Thrown at open-sse/handlers/imageProviders/nanobanana.js:47
if (isEdit) {
const urls = Array.isArray(body.images) ? body.images.filter(Boolean) : [];
if (body.image) urls.push(body.image);
req.imageUrls = urls;
}
return req;
},
// 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
- Check the reported HTTP status: 401/403 → refresh or replace the NanoBanana API key/accessToken in the provider credentials, then retry.
- 429 → reduce concurrency or add client-side backoff before re-submitting the image request.
- 404 → the taskId expired or the poll URL is wrong; verify imageConfig.pollUrl in PROVIDER_MEDIA['nanobanana'] and re-submit a fresh generation.
- 5xx → retry after a short wait; if persistent, check the provider's status page or network/proxy connectivity.
- If transient statuses are frequent for you, patch/wrap parseResponse to tolerate (retry a few times) 429/5xx poll responses instead of throwing on the first non-ok.
Example fix
// before
const r = await fetch(pollUrl, { headers });
if (!r.ok) throw new Error(`NanoBanana status ${r.status}`);
// after
const r = await fetch(pollUrl, { headers });
if (!r.ok) {
if ((r.status === 429 || r.status >= 500) && ++pollErrors <= 3) continue; // tolerate transient poll errors
throw new Error(`NanoBanana status ${r.status}`);
}
pollErrors = 0; Defensive patterns
Strategy: try-catch
Validate before calling
// Before calling: ensure a NanoBanana credential exists and the poll config is present
const cfg = PROVIDER_MEDIA["nanobanana"]?.imageConfig;
if (!cfg?.baseUrl || !cfg?.pollUrl) throw new Error("NanoBanana imageConfig (baseUrl/pollUrl) missing");
if (!(creds?.apiKey || creds?.accessToken)) throw new Error("NanoBanana API key/accessToken missing"); Type guard
function isOkResponse(r) { return typeof r === "object" && r !== null && typeof r.ok === "boolean" && r.ok; } Try / catch
try {
const result = await imageProvider.parseResponse(response, { headers });
} catch (e) {
if (/^NanoBanana status \d+$/.test(e.message)) {
const status = Number(e.message.match(/\d+/)?.[0]);
if (status === 401 || status === 403) await refreshCredentials();
// retry submission for transient 429/5xx
if (status === 429 || status >= 500) return retryWithBackoff();
}
throw e;
} Prevention
- Rotate/refresh NanoBanana credentials before long async generations, not just at submit time.
- Keep imageConfig.pollUrl in PROVIDER_MEDIA in sync with the provider's current API.
- Throttle concurrent image generations to avoid poll-endpoint 429s.
- Monitor for this error pattern and alert on repeated non-ok poll statuses per provider.
When it happens
Trigger: A `fetch(pollUrl, { headers })` inside the polling loop at open-sse/handlers/imageProviders/nanobanana.js:46-47 returns a non-ok status (e.g. 401 because the Bearer token expired mid-poll, 404 because the taskId expired upstream, 429 rate limit, or 5xx upstream outage).
Common situations: Long-running generations outliving a short-lived API key/accessToken; the provider expiring or garbage-collecting the taskId; provider-side rate limiting under concurrent image requests; temporary upstream 5xx during the polling window (POLL_TIMEOUT_MS); misconfigured pollUrl in PROVIDER_MEDIA['nanobanana'].imageConfig hitting a wrong host.
Related errors
- Runway status ${r.status}
- Runway: no task id returned
- MiMo bootstrap failed: ${response.status}
- BFL: no polling_url returned
- NanoBanana generation failed
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/9993ce3e0af7163e.
Report an issue: GitHub.