nexu-io/open-design · error · Error

grok video submit ${submitResp.status}: ${truncate(submitTex

Error message

grok video submit ${submitResp.status}: ${truncate(submitText, 240)}

What it means

Thrown when POST {baseUrl}/videos/generations on xAI returned a non-2xx status. The message includes the HTTP status and the first 240 chars (truncated) of the response body so the operator can read xAI's error text. This is the upstream-rejection guard for the Grok video submit step, distinct from the JSON-parse error [473] that follows.

Source

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

  };
  if (ctx.imageRef && ctx.imageRef.dataUrl) {
    // grok-imagine-video accepts a base64 data URI in `image` for i2v.
    // Same surface as Seedance — the dispatcher already produced the
    // data URL via resolveProjectImage, so we just hand it through.
    body.image = ctx.imageRef.dataUrl;
  }

  const submitResp = await fetch(`${baseUrl}/videos/generations`, withMediaRequestInit(ctx, {
    method: 'POST',
    headers: {
      'authorization': `Bearer ${credentials.apiKey}`,
      'content-type': 'application/json',
    },
    body: JSON.stringify(body),
  }));
  const submitText = await submitResp.text();
  if (!submitResp.ok) {
    throw new Error(`grok video submit ${submitResp.status}: ${truncate(submitText, 240)}`);
  }
  let submitData: any;
  try {
    submitData = JSON.parse(submitText);
  } catch {
    throw new Error(`grok video non-JSON: ${truncate(submitText, 200)}`);
  }

  // Two paths: (a) the API returned the finished video synchronously
  // (cached/short jobs), in which case we skip polling; (b) we got an
  // {id, status:'pending'} stub and need to poll GET /videos/{id}
  // until status flips to done/failed/expired.
  let videoUrl = submitData?.video?.url || null;
  let lastStatus = submitData?.status || '';
  const requestId = submitData?.id || submitData?.request_id || null;

  if (!videoUrl && requestId) {
    const startedAt = Date.now();

View on GitHub (pinned to 5be4028344)

Solutions

  1. Read the truncated body in the error message — xAI states the exact rejection reason (invalid_params, content_policy, insufficient_quota).
  2. If 400 invalid_params: verify duration ≤ 15s and aspect ratio is in xAI's accepted set; the daemon re-clamps but a manual override could bypass it.
  3. If 401/403: refresh the credential — for OAuth re-run `hermes auth add xai-oauth`; for API key rotate XAI_API_KEY.
  4. If 429/quota: wait for reset or upgrade your xAI plan.
  5. If 5xx: retry with backoff; check xAI status page.

Example fix

// before — duration not clamped at the call site
body = { duration: 30 } // → HTTP 400 from xAI

// after — clamp before submit (mirrors the dispatcher's clamp)
body = { duration: Math.min(ctx.length || 5, 15) }
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-validate the Grok submit body against xAI's hard constraints so the
// request doesn't bounce off a 400.
function validateGrokSubmitBody(body: Record<string, unknown>): string[] {
  const errors: string[] = [];
  const dur = body.duration as number | undefined;
  if (typeof dur !== 'number' || dur < 1 || dur > 15) errors.push('duration must be 1..15s');
  const aspect = body.aspect as string | undefined;
  if (aspect && !['1:1','16:9','9:16','4:3','3:4','3:2','2:3','2:1'].includes(aspect)) errors.push(`unsupported aspect ${aspect}`);
  return errors;
}

Try / catch

// Classify the submit failure so callers retry only idempotent classes.
try {
  await renderGrokVideo(ctx, creds, onProgress);
} catch (e) {
  const msg = String((e as Error).message || e);
  const m = msg.match(/grok video submit (\d+)/);
  if (m) {
    const status = Number(m[1]);
    if (status === 429 || status >= 500) { await renderGrokVideo(ctx, creds, onProgress); return; }
    if (status === 401 || status === 403) throw new Error('xAI credential invalid — re-sign-in via `hermes auth add xai-oauth` or rotate XAI_API_KEY');
  }
  throw e;
}

Prevention

When it happens

Trigger: xAI rejects the video generation request: invalid/missing auth (401/403), quota or rate-limit (422/429), malformed body — e.g. duration >15s slipped through (400), unsupported aspect ratio (400), or xAI-side 5xx.

Common situations: Duration clamping slipped (request sent with length > 15); aspect ratio xAI doesn't accept; XAI_API_KEY revoked; free-tier quota exhausted; prompt trips xAI moderation; transient xAI 5xx.

Related errors


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