nexu-io/open-design · error · Error

grok video timed out after ${elapsedSec}s waiting for status

Error message

grok video timed out after ${elapsedSec}s waiting for status=done (last status: ${lastStatus || 'pending'}, ceiling ${ceilingSec}s). If your jobs legitimately need longer, raise OD_GROK_VIDEO_MAX_POLL_MS.

What it means

Thrown when the Grok polling loop exits its window (startedAt + maxMs) without videoUrl being set and without hitting a failed/expired terminal status. maxMs defaults per mode (i2v vs t2v) and is env-overridable via OD_GROK_VIDEO_MAX_POLL_MS. The message reports elapsed seconds, last observed status, and the ceiling so the operator can decide whether to raise the limit. Distinct from [476] (definitive failure) and [478] (no id to poll at all).

Source

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

      }
      if (lastStatus === 'done' || lastStatus === 'succeeded') {
        videoUrl = pollData?.video?.url || null;
        break;
      }
      if (lastStatus === 'failed' || lastStatus === 'expired') {
        const reasonRaw = pollData?.error?.message || pollData?.error || lastStatus;
        const reason = typeof reasonRaw === 'string' ? reasonRaw : JSON.stringify(reasonRaw);
        throw new Error(`grok task ${lastStatus}: ${reason}`);
      }
    }
    // Loop exited without a videoUrl. Distinguish the two reachable
    // cases so operators know which lever to pull: bumping the poll
    // ceiling (timeout) vs filing a bug against the upstream contract
    // (status=done but no video.url).
    if (!videoUrl) {
      const elapsedSec = Math.round((Date.now() - startedAt) / 1000);
      const ceilingSec = Math.round(maxMs / 1000);
      throw new Error(
        `grok video timed out after ${elapsedSec}s waiting for status=done `
        + `(last status: ${lastStatus || 'pending'}, ceiling ${ceilingSec}s). `
        + `If your jobs legitimately need longer, raise OD_GROK_VIDEO_MAX_POLL_MS.`,
      );
    }
  }

  if (!videoUrl) {
    // Submit returned neither an inline video.url nor a request_id —
    // upstream broke its own contract. Surfacing the last status helps
    // pinpoint whether it was a transient API blip or a malformed
    // response we should add a parser branch for.
    throw new Error(
      `grok video submit returned no inline video and no request_id to poll `
      + `(status=${lastStatus || 'unknown'})`,
    );
  }

View on GitHub (pinned to 5be4028344)

Solutions

  1. Raise the ceiling: set OD_GROK_VIDEO_MAX_POLL_MS higher in the daemon environment if your jobs legitimately run long.
  2. Reduce requested duration or pick a faster path (t2v vs i2v) so xAI finishes within the default window.
  3. Check xAI status/dashboard — confirm the job actually stalled vs. is still processing; retry if it was queue backlog.
  4. If lastStatus shows a value that isn't 'done'/'succeeded' but also isn't failed/expired, inspect whether xAI added a new success status the parser should treat as complete.

Example fix

// before
// OD_GROK_VIDEO_MAX_POLL_MS unset → default ceiling per mode

// after (operator)
export OD_GROK_VIDEO_MAX_POLL_MS=1800000
# restart the daemon so the env var is picked up
Defensive patterns

Strategy: retry

Validate before calling

// Before invoking renderGrokVideo, sanity-check the configured ceiling against
// the requested job size so callers get a fast config error instead of a long
// timeout.
function assertGrokCeilingOk(mode: 't2v' | 'i2v', requestedLengthSec: number) {
  const maxMs = Number(process.env.OD_GROK_VIDEO_MAX_POLL_MS)
    || (mode === 'i2v' ? 12 * 60 * 1000 : 8 * 60 * 1000);
  const estimatedMs = requestedLengthSec * 45 * 1000; // ~45s/s, conservative for Grok
  if (estimatedMs > maxMs) {
    throw new Error(
      `requested ${requestedLengthSec}s grok ${mode} likely exceeds OD_GROK_VIDEO_MAX_POLL_MS (${Math.round(maxMs / 1000)}s); raise the env var or shorten the request.`,
    );
  }
}

Try / catch

// Catch the timeout specifically and surface the raise-the-ceiling hint to the user.
try {
  await renderGrokVideo(ctx, creds, onProgress);
} catch (e) {
  const msg = String((e as Error).message || e);
  if (msg.startsWith('grok video timed out')) {
    throw new Error(`Grok video generation timed out. Ask the operator to raise OD_GROK_VIDEO_MAX_POLL_MS. Detail: ${msg}`);
  }
  throw e;
}

Prevention

When it happens

Trigger: Grok video generation where status stays pending/processing inside the poll loop for longer than maxMs (default mode-dependent, capped). Triggered when the while-loop exits with videoUrl still null and lastStatus never reached 'done'/'succeeded' nor 'failed'/'expired'.

Common situations: Long Grok video jobs that legitimately exceed the default ceiling; xAI queue backlog; default ceiling too low for the requested duration/complexity; status string xAI returns doesn't match the 'done'/'succeeded' matcher so the loop never breaks.

Understand the failure class

Related errors


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