{"record":{"id":"9a27ce6876ac5a95","repo":"nexu-io/open-design","slug":"grok-poll-non-json-truncate-polltext-200","errorCode":null,"errorMessage":"grok poll non-JSON: ${truncate(pollText, 200)}","messagePattern":"grok poll non-JSON: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/daemon/src/media/index.ts","lineNumber":2382,"sourceCode":"        : 8 * 60 * 1000;\n    if (typeof onProgress === 'function') {\n      const mode = ctx.imageRef ? 'i2v' : 't2v';\n      onProgress(`grok ${mode} task ${requestId} accepted; polling status…`);\n    }\n    while (Date.now() - startedAt < maxMs) {\n      await sleep(4000);\n      const pollResp = await fetch(`${baseUrl}/videos/${encodeURIComponent(requestId)}`, withMediaRequestInit(ctx, {\n        headers: { 'authorization': `Bearer ${credentials.apiKey}` },\n      }));\n      const pollText = await pollResp.text();\n      if (!pollResp.ok) {\n        throw new Error(`grok poll ${pollResp.status}: ${truncate(pollText, 240)}`);\n      }\n      let pollData: any;\n      try {\n        pollData = JSON.parse(pollText);\n      } catch {\n        throw new Error(`grok poll non-JSON: ${truncate(pollText, 200)}`);\n      }\n      lastStatus = pollData.status || '';\n      if (typeof onProgress === 'function') {\n        const elapsedSec = Math.round((Date.now() - startedAt) / 1000);\n        onProgress(`grok task ${requestId} status=${lastStatus || 'pending'} (elapsed ${elapsedSec}s)`);\n      }\n      if (lastStatus === 'done' || lastStatus === 'succeeded') {\n        videoUrl = pollData?.video?.url || null;\n        break;\n      }\n      if (lastStatus === 'failed' || lastStatus === 'expired') {\n        const reasonRaw = pollData?.error?.message || pollData?.error || lastStatus;\n        const reason = typeof reasonRaw === 'string' ? reasonRaw : JSON.stringify(reasonRaw);\n        throw new Error(`grok task ${lastStatus}: ${reason}`);\n      }\n    }\n    // Loop exited without a videoUrl. Distinguish the two reachable\n    // cases so operators know which lever to pull: bumping the poll","sourceCodeStart":2364,"sourceCodeEnd":2400,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/media/index.ts#L2364-L2400","documentation":"Thrown when a Grok poll returned 2xx but the body failed JSON.parse. The poll endpoint returned a non-JSON payload (HTML, empty, plain text) with a success status. The message includes the first 200 chars (truncated) so the operator can identify the unexpected content. Distinct from [474] which fires on non-2xx status.","triggerScenarios":"pollResp.ok is true but JSON.parse(pollText) throws — xAI or an intermediary returned 200 with HTML/empty/plain text during a poll iteration. Usually a transient gateway artifact.","commonSituations":"Cloudflare challenge served mid-poll; xAI gateway returning a cached HTML error during maintenance; reverse-proxy rewriting; transient empty 200 responses under load.","solutions":["Inspect the truncated pollText in the error — HTML/doctype indicates a gateway page; the loop would have tolerated a retry if the parse error didn't throw.","Patch the loop to tolerate transient non-JSON (continue instead of throw) within the poll ceiling, then retry.","If persistent, verify baseUrl and network path; capture full response and report to xAI.","Retry the entire job — transient gateway blips usually don't recur."],"exampleFix":"// before\ntry {\n  pollData = JSON.parse(pollText);\n} catch {\n  throw new Error(`grok poll non-JSON: ${truncate(pollText, 200)}`);\n}\n\n// after — tolerate transient non-JSON within the ceiling\ntry {\n  pollData = JSON.parse(pollText);\n} catch {\n  if (Date.now() - startedAt < maxMs) { await sleep(4000); continue; }\n  throw new Error(`grok poll non-JSON: ${truncate(pollText, 200)}`);\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Non-JSON poll responses are usually transient gateway blips — tolerate them\n// inside the ceiling, only throw when time is up.\ntry {\n  pollData = JSON.parse(pollText);\n} catch {\n  if (Date.now() - startedAt < maxMs) { await sleep(4000); continue; }\n  throw new Error(`grok poll non-JSON: ${truncate(pollText, 200)}`);\n}","preventionTips":["Don't let a single non-JSON poll response abort a multi-minute job — continue inside the ceiling.","Log non-JSON occurrences so you can detect a rising gateway-error rate without losing jobs.","Send `Accept: application/json` on polls to discourage HTML interstitials."],"tags":["grok","xai","json-parse","gateway","transient"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}