nexu-io/open-design · error · Error

grok poll ${pollResp.status}: ${truncate(pollText, 240)}

Error message

grok poll ${pollResp.status}: ${truncate(pollText, 240)}

What it means

Thrown inside the Grok polling loop when GET {baseUrl}/videos/{requestId} returns a non-2xx status. The message includes the HTTP status and the first 240 chars (truncated) of the poll body so the operator can read xAI's reason. Distinct from [475] (parse error), [476] (terminal failed/expired status), and [477] (timeout): this is the transport-level rejection of a single poll.

Source

Thrown at apps/daemon/src/media/index.ts:2376

  if (!videoUrl && requestId) {
    const startedAt = Date.now();
    const configuredMaxMs = Number(process.env.OD_GROK_VIDEO_MAX_POLL_MS);
    const maxMs =
      Number.isFinite(configuredMaxMs) && configuredMaxMs >= 60_000
        ? configuredMaxMs
        : 8 * 60 * 1000;
    if (typeof onProgress === 'function') {
      const mode = ctx.imageRef ? 'i2v' : 't2v';
      onProgress(`grok ${mode} task ${requestId} accepted; polling status…`);
    }
    while (Date.now() - startedAt < maxMs) {
      await sleep(4000);
      const pollResp = await fetch(`${baseUrl}/videos/${encodeURIComponent(requestId)}`, withMediaRequestInit(ctx, {
        headers: { 'authorization': `Bearer ${credentials.apiKey}` },
      }));
      const pollText = await pollResp.text();
      if (!pollResp.ok) {
        throw new Error(`grok poll ${pollResp.status}: ${truncate(pollText, 240)}`);
      }
      let pollData: any;
      try {
        pollData = JSON.parse(pollText);
      } catch {
        throw new Error(`grok poll non-JSON: ${truncate(pollText, 200)}`);
      }
      lastStatus = pollData.status || '';
      if (typeof onProgress === 'function') {
        const elapsedSec = Math.round((Date.now() - startedAt) / 1000);
        onProgress(`grok task ${requestId} status=${lastStatus || 'pending'} (elapsed ${elapsedSec}s)`);
      }
      if (lastStatus === 'done' || lastStatus === 'succeeded') {
        videoUrl = pollData?.video?.url || null;
        break;
      }
      if (lastStatus === 'failed' || lastStatus === 'expired') {
        const reasonRaw = pollData?.error?.message || pollData?.error || lastStatus;

View on GitHub (pinned to 5be4028344)

Solutions

  1. Read the truncated poll body in the error — xAI often includes a reason (rate_limited, not_found).
  2. If 429 persistent: the 4000ms poll interval may be too aggressive for your account tier; raise the interval or wait for quota reset.
  3. If 404: requestId is gone — resubmit the entire generation; if it 404s immediately, the submit returned a bogus id (see [478]).
  4. If 401/403: refresh the xAI credential and resubmit.
  5. If 5xx: retry the whole job after a short backoff.

Example fix

// before — single poll failure throws immediately
if (!pollResp.ok) {
  throw new Error(`grok poll ${pollResp.status}: ${truncate(pollText, 240)}`);
}

// after — tolerate transient 5xx/429 within the poll ceiling
if (!pollResp.ok) {
  if ((pollResp.status === 429 || pollResp.status >= 500) && Date.now() - startedAt < maxMs) {
    await sleep(8000); // back off harder
    continue;
  }
  throw new Error(`grok poll ${pollResp.status}: ${truncate(pollText, 240)}`);
}
Defensive patterns

Strategy: retry

Try / catch

// Poll failures are dominated by transient 5xx/429; tolerate them inside the
// poll window, only throw on auth or definitive not-found.
while (Date.now() - startedAt < maxMs) {
  await sleep(4000);
  const pollResp = await fetch(`${baseUrl}/videos/${encodeURIComponent(requestId)}`, withMediaRequestInit(ctx, { headers: { authorization: `Bearer ${credentials.apiKey}` } }));
  const pollText = await pollResp.text();
  if (!pollResp.ok) {
    if ((pollResp.status === 429 || pollResp.status >= 500) && Date.now() - startedAt < maxMs) { await sleep(8000); continue; }
    throw new Error(`grok poll ${pollResp.status}: ${truncate(pollText, 240)}`);
  }
  // ... parse + handle
}

Prevention

When it happens

Trigger: Polling loop calls /videos/{id} and pollResp.ok is false — 401/403 (key lost scope mid-job), 404 (requestId doesn't exist or expired), 429 (rate-limit on polls), or 5xx (xAI outage). The poll loop sleeps 4000ms between attempts.

Common situations: Polling rate exceeded xAI's limit (429); requestId expired from xAI's retention before completion (404); API key revoked mid-job (401); transient xAI 5xx during the poll window.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/02c17d38020d212d. Report an issue: GitHub.