{"record":{"id":"31439af6ae52a289","repo":"nexu-io/open-design","slug":"grok-video-submit-submitresp-status-truncate","errorCode":null,"errorMessage":"grok video submit ${submitResp.status}: ${truncate(submitText, 240)}","messagePattern":"grok video submit (.+?): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/daemon/src/media/index.ts","lineNumber":2341,"sourceCode":"  };\n  if (ctx.imageRef && ctx.imageRef.dataUrl) {\n    // grok-imagine-video accepts a base64 data URI in `image` for i2v.\n    // Same surface as Seedance — the dispatcher already produced the\n    // data URL via resolveProjectImage, so we just hand it through.\n    body.image = ctx.imageRef.dataUrl;\n  }\n\n  const submitResp = await fetch(`${baseUrl}/videos/generations`, withMediaRequestInit(ctx, {\n    method: 'POST',\n    headers: {\n      'authorization': `Bearer ${credentials.apiKey}`,\n      'content-type': 'application/json',\n    },\n    body: JSON.stringify(body),\n  }));\n  const submitText = await submitResp.text();\n  if (!submitResp.ok) {\n    throw new Error(`grok video submit ${submitResp.status}: ${truncate(submitText, 240)}`);\n  }\n  let submitData: any;\n  try {\n    submitData = JSON.parse(submitText);\n  } catch {\n    throw new Error(`grok video non-JSON: ${truncate(submitText, 200)}`);\n  }\n\n  // Two paths: (a) the API returned the finished video synchronously\n  // (cached/short jobs), in which case we skip polling; (b) we got an\n  // {id, status:'pending'} stub and need to poll GET /videos/{id}\n  // until status flips to done/failed/expired.\n  let videoUrl = submitData?.video?.url || null;\n  let lastStatus = submitData?.status || '';\n  const requestId = submitData?.id || submitData?.request_id || null;\n\n  if (!videoUrl && requestId) {\n    const startedAt = Date.now();","sourceCodeStart":2323,"sourceCodeEnd":2359,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/media/index.ts#L2323-L2359","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Read the truncated body in the error message — xAI states the exact rejection reason (invalid_params, content_policy, insufficient_quota).","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.","If 401/403: refresh the credential — for OAuth re-run `hermes auth add xai-oauth`; for API key rotate XAI_API_KEY.","If 429/quota: wait for reset or upgrade your xAI plan.","If 5xx: retry with backoff; check xAI status page."],"exampleFix":"// before — duration not clamped at the call site\nbody = { duration: 30 } // → HTTP 400 from xAI\n\n// after — clamp before submit (mirrors the dispatcher's clamp)\nbody = { duration: Math.min(ctx.length || 5, 15) }","handlingStrategy":"try-catch","validationCode":"// Pre-validate the Grok submit body against xAI's hard constraints so the\n// request doesn't bounce off a 400.\nfunction validateGrokSubmitBody(body: Record<string, unknown>): string[] {\n  const errors: string[] = [];\n  const dur = body.duration as number | undefined;\n  if (typeof dur !== 'number' || dur < 1 || dur > 15) errors.push('duration must be 1..15s');\n  const aspect = body.aspect as string | undefined;\n  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}`);\n  return errors;\n}","typeGuard":null,"tryCatchPattern":"// Classify the submit failure so callers retry only idempotent classes.\ntry {\n  await renderGrokVideo(ctx, creds, onProgress);\n} catch (e) {\n  const msg = String((e as Error).message || e);\n  const m = msg.match(/grok video submit (\\d+)/);\n  if (m) {\n    const status = Number(m[1]);\n    if (status === 429 || status >= 500) { await renderGrokVideo(ctx, creds, onProgress); return; }\n    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');\n  }\n  throw e;\n}","preventionTips":["Always clamp duration at the call site (≤15s) before building the submit body — the renderer re-clamps but a manual override can bypass it.","Monitor xAI quota separately for video (costlier than chat); alert before exhaustion.","Run a smoke render after every xAI API version bump — response shapes change with little notice."],"tags":["grok","xai","http-status","upstream-error","api-rejection"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}