{"record":{"id":"cdfedee0e1a06c9d","repo":"nexu-io/open-design","slug":"openrouter-video-non-json-truncate-submittext","errorCode":null,"errorMessage":"openrouter video non-JSON: ${truncate(submitText, 200)}","messagePattern":"openrouter video non-JSON: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/daemon/src/media/index.ts","lineNumber":2037,"sourceCode":"      'content-type': 'application/json',\n      // OpenRouter attribution headers per\n      // https://openrouter.ai/docs/app-attribution\n      'HTTP-Referer': 'https://opendesign.dev',\n      'X-Title': 'Open Design',\n    },\n    body: JSON.stringify(body),\n  }));\n  const submitText = await submitResp.text();\n  if (!submitResp.ok) {\n    throw new Error(\n      `openrouter video submit ${submitResp.status}: ${truncate(submitText, 240)}`,\n    );\n  }\n  let submitData: any;\n  try {\n    submitData = JSON.parse(submitText);\n  } catch {\n    throw new Error(`openrouter video non-JSON: ${truncate(submitText, 200)}`);\n  }\n\n  const jobId = submitData?.id;\n  const pollingUrl = submitData?.polling_url;\n  if (!jobId || !pollingUrl) {\n    throw new Error(\n      `openrouter video submit returned no job id or polling_url: ${truncate(submitText, 200)}`,\n    );\n  }\n\n  // ── Step 2: Poll until completion ──────────────────────────────────\n  const startedAt = Date.now();\n  const configuredMaxMs = Number(process.env.OD_OPENROUTER_VIDEO_MAX_POLL_MS);\n  const maxMs =\n    Number.isFinite(configuredMaxMs) && configuredMaxMs >= 60_000\n      ? configuredMaxMs\n      : 30 * 60 * 1000; // 30 minutes default\n  const configuredPollIntervalMs = Number(process.env.OD_OPENROUTER_VIDEO_POLL_INTERVAL_MS);","sourceCodeStart":2019,"sourceCodeEnd":2055,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/media/index.ts#L2019-L2055","documentation":"Thrown when the video submit endpoint returned HTTP 2xx but JSON.parse failed on the body. OpenRouter's async video submit is contractually JSON (carrying id and polling_url); a non-JSON 2xx means an interceptor rewrote the response or baseUrl is misrouted to a host returning HTML.","triggerScenarios":"Proxy/CDN served an HTML page with 200, baseUrl points at a wrong host, or a captive portal intercepted the request. The guard stops the subsequent id/polling_url extraction from running on non-JSON input.","commonSituations":"Corporate proxy blocking openrouter.ai with an HTML block page, baseUrl typo hitting a parked/marketing domain, or a transient CDN interstitial.","solutions":["Inspect the truncated body in the error — HTML/doctype tags indicate interception/misroute.","Reset credentials.baseUrl to the default or remove it.","Whitelist openrouter.ai through any HTTP proxy.","Retry once for transient interstitials."],"exampleFix":"// before\ntry { submitData = JSON.parse(submitText); }\ncatch { throw new Error(`openrouter video non-JSON: ${truncate(submitText, 200)}`); }\n\n// after — distinguish HTML interception\ntry { submitData = JSON.parse(submitText); }\ncatch {\n  const html = /<html|<!doctype/i.test(submitText);\n  throw new Error(\n    `openrouter video ${html ? 'returned HTML (interception?)' : 'non-JSON'}: ${truncate(submitText, 200)}`,\n  );\n}","handlingStrategy":"validation","validationCode":"function looksLikeHtmlBody(text: string): boolean {\n  return /<html|<!doctype|<title>/i.test(text.slice(0, 200));\n}\n\nif (looksLikeHtmlBody(submitText)) {\n  throw new Error(`openrouter video submit returned HTML (proxy/interception or wrong baseUrl): ${truncate(submitText, 200)}`);\n}","typeGuard":"function isJsonContentType(resp: Response): boolean {\n  const ct = resp.headers.get('content-type') || '';\n  return ct.includes('application/json') || ct.includes('+json');\n}","tryCatchPattern":"try {\n  submitData = JSON.parse(submitText);\n} catch {\n  if (looksLikeHtmlBody(submitText)) {\n    throw new Error(`openrouter video returned HTML (interception?): ${truncate(submitText, 200)}`);\n  }\n  throw new Error(`openrouter video non-JSON: ${truncate(submitText, 200)}`);\n}","preventionTips":["Validate content-type and body prefix before parsing.","Reset baseUrl to the default OpenRouter endpoint when interception is suspected.","Whitelist openrouter.ai through corporate proxies.","Log the first 200 chars of non-JSON bodies for diagnosis."],"tags":["openrouter","video-generation","response-shape","network","provider-api"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}