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

  1. Check the reported HTTP status: 401/403 → refresh or replace the NanoBanana API key/accessToken in the provider credentials, then retry.
  2. 429 → reduce concurrency or add client-side backoff before re-submitting the image request.
  3. 404 → the taskId expired or the poll URL is wrong; verify imageConfig.pollUrl in PROVIDER_MEDIA['nanobanana'] and re-submit a fresh generation.
  4. 5xx → retry after a short wait; if persistent, check the provider's status page or network/proxy connectivity.
  5. 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

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


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/9993ce3e0af7163e. Report an issue: GitHub.