{"record":{"id":"ebd65dc2ebb4cd99","repo":"nexu-io/open-design","slug":"openrouter-poll-pollresp-status-truncate-pol","errorCode":null,"errorMessage":"openrouter poll ${pollResp.status}: ${truncate(pollText, 240)}","messagePattern":"openrouter poll (.+?): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/daemon/src/media/index.ts","lineNumber":2084,"sourceCode":"    onProgress(\n      `openrouter ${mode} job ${jobId} (${wireModel}) accepted; polling status…`,\n    );\n  }\n\n  while (Date.now() - startedAt < maxMs) {\n    if (pollIntervalMs > 0) {\n      await sleep(pollIntervalMs);\n    }\n    const pollResp = await fetch(pollingUrl, withMediaRequestInit(ctx, {\n      headers: {\n        'authorization': `Bearer ${credentials.apiKey}`,\n        'HTTP-Referer': 'https://opendesign.dev',\n        'X-Title': 'Open Design',\n      },\n    }));\n    const pollText = await pollResp.text();\n    if (!pollResp.ok) {\n      throw new Error(\n        `openrouter poll ${pollResp.status}: ${truncate(pollText, 240)}`,\n      );\n    }\n    let pollData: any;\n    try {\n      pollData = JSON.parse(pollText);\n    } catch {\n      throw new Error(`openrouter poll non-JSON: ${truncate(pollText, 200)}`);\n    }\n\n    lastStatus = pollData?.status || '';\n    if (typeof onProgress === 'function') {\n      const elapsedSec = Math.round((Date.now() - startedAt) / 1000);\n      onProgress(\n        `openrouter job ${jobId} status=${lastStatus || 'pending'} (elapsed ${elapsedSec}s)`,\n      );\n    }\n","sourceCodeStart":2066,"sourceCodeEnd":2102,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/media/index.ts#L2066-L2102","documentation":"Thrown inside the video polling loop when GET pollingUrl returns non-2xx. Each iteration fetches pollText and, on failure, embeds pollResp.status plus the first 240 chars. A failed poll aborts the whole job rather than continuing — the daemon cannot determine job state from a non-2xx.","triggerScenarios":"polling_url expired or was revoked mid-job, OpenRouter rate-limits the polling endpoint (429), the job was garbage-collected on the provider side (404), auth issues (401/403), or upstream 5xx during polling.","commonSituations":"Long-running jobs whose polling_url TTL lapses before completion, aggressive poll intervals triggering 429, provider-side job expiry, or transient 5xx during a provider incident.","solutions":["Tune OD_OPENROUTER_VIDEO_POLL_INTERVAL_MS upward to avoid 429s on the polling endpoint.","If polling_url expiry is the cause, lower OD_OPENROUTER_VIDEO_MAX_POLL_MS expectations or pick a faster video model.","On 5xx, the existing loop does not retry — wrap the poll in a small backoff before giving up.","On 401/403 re-check the key; on 404 the job is gone — restart the render from scratch."],"exampleFix":"// before\nif (!pollResp.ok) {\n  throw new Error(`openrouter poll ${pollResp.status}: ${truncate(pollText, 240)}`);\n}\n\n// after — tolerate transient 5xx/429 with bounded retries inside the loop\nlet pollResp = await fetch(pollingUrl, /* ... */);\nlet pollText = await pollResp.text();\nfor (let attempt = 0; attempt < 2 && !pollResp.ok && (pollResp.status === 429 || pollResp.status >= 500); attempt++) {\n  await sleep(2000 * (attempt + 1));\n  pollResp = await fetch(pollingUrl, /* ... */);\n  pollText = await pollResp.text();\n}\nif (!pollResp.ok) {\n  throw new Error(`openrouter poll ${pollResp.status}: ${truncate(pollText, 240)}`);\n}","handlingStrategy":"retry","validationCode":"// Bound the polling interval to avoid 429s\nfunction safePollIntervalMs(env = process.env): number {\n  const v = Number(env.OD_OPENROUTER_VIDEO_POLL_INTERVAL_MS);\n  return Number.isFinite(v) && v >= 5000 ? v : 10_000; // never poll faster than every 5s\n}\n\nfunction isRetryablePollStatus(status: number): boolean {\n  return status === 429 || status === 408 || status >= 500;\n}","typeGuard":"function isPollUrlStillValid(pollingUrl: string): boolean {\n  // crude check — must still be https and non-empty\n  return /^https:\\/\\//.test(pollingUrl);\n}","tryCatchPattern":"let pollData: any;\nfor (let attempt = 0; attempt < 3; attempt++) {\n  const pollResp = await fetch(pollingUrl, /* auth headers */);\n  if (pollResp.ok) { pollData = JSON.parse(await pollResp.text()); break; }\n  if (isRetryablePollStatus(pollResp.status) && attempt < 2) {\n    await sleep(2000 * (attempt + 1));\n    continue;\n  }\n  throw new Error(`openrouter poll ${pollResp.status}: ${truncate(await pollResp.text(), 240)}`);\n}","preventionTips":["Tune OD_OPENROUTER_VIDEO_POLL_INTERVAL_MS upward (>=5s) to avoid 429 on the polling endpoint.","Retry transient 5xx/429 inside the loop before aborting the whole job.","If polling URLs expire mid-job, choose a faster video model.","On 404 mid-poll, restart the render rather than continuing."],"tags":["openrouter","video-generation","async-polling","http-status","network","provider-api"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}