{"record":{"id":"caebebf4cc59af13","repo":"jackwener/OpenCLI","slug":"rendered-video-kind-must-be-video-social-or-gif","errorCode":null,"errorMessage":"Rendered video kind must be video-social or gif","messagePattern":"Rendered video kind must be video-social or gif","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/midjourney/utils.js","lineNumber":735,"sourceCode":"  const media = await fetchMediaThroughPage(page, url, 'video/');\n  const actualMime = sniffMediaMime(media.buffer);\n  if (actualMime !== 'video/mp4') {\n    throw new CommandExecutionError(`Midjourney raw video returned invalid MP4 bytes from ${url}`);\n  }\n  if (media.mime !== actualMime) {\n    log.warn(`Midjourney CDN reported ${media.mime || 'unknown'} for ${url}; detected ${actualMime} from file bytes`);\n  }\n  await writeMediaBuffer(filePath, media.buffer);\n  return { index, kind: 'video-raw', filePath, bytes: media.buffer.length, url, mime: actualMime, cached: false };\n}\n\nexport async function downloadRenderedVideo(page, jobId, index, kind, outputDir, force = false) {\n  const config = kind === 'video-social'\n    ? { label: 'Download for Social', extension: '.mp4', mime: 'video/mp4' }\n    : kind === 'gif'\n      ? { label: 'Download as GIF', extension: '.gif', mime: 'image/gif' }\n      : null;\n  if (!config) throw new ArgumentError('Rendered video kind must be video-social or gif');\n  await fs.mkdir(outputDir, { recursive: true });\n  const filePath = path.join(outputDir, `${jobId}_${index + 1}_${kind.replace('video-', '')}${config.extension}`);\n  const existing = await existingMedia(filePath, force, config.mime);\n  if (existing) return { index, kind, filePath, bytes: existing.size, url: null, mime: config.mime, cached: true };\n\n  await page.goto(jobUrl(jobId, index));\n  await page.wait({ selector: 'video[src]', timeout: 20 });\n  await page.click('button[title=\"Options\"]');\n  await page.wait(0.3);\n  const marked = unwrapEvaluateResult(await page.evaluate((label) => {\n    document.querySelectorAll('[data-opencli-video-download]').forEach((node) => node.removeAttribute('data-opencli-video-download'));\n    const button = [...document.querySelectorAll('button[role=\"menuitem\"],button')]\n      .find((node) => node.textContent?.trim() === label && node.getBoundingClientRect().width > 0);\n    if (!button) return false;\n    button.setAttribute('data-opencli-video-download', '1');\n    return true;\n  }, config.label));\n  if (!marked) throw new CommandExecutionError(`Midjourney did not expose \"${config.label}\" for video ${jobId}`);","sourceCodeStart":717,"sourceCodeEnd":753,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/midjourney/utils.js#L717-L753","documentation":"An ArgumentError raised when downloadRenderedVideo is called with a kind argument other than 'video-social' or 'gif'. These are the only two rendered-download menu labels the function has configurations for; anything else would produce a null config and an unhandled null dereference later, so the function fails fast.","triggerScenarios":"Calling downloadRenderedVideo(page, jobId, index, kind, outputDir, force) with kind values like 'video', 'mp4', 'raw', or a typo such as 'videosocial'.","commonSituations":"Mapping a CLI flag or user-supplied string directly to kind without normalizing; mistaking the raw video path (which uses kind 'video-raw') for the rendered path; renaming the kind constants in a refactor without updating callers.","solutions":["Pass exactly 'video-social' or 'gif' as kind","Normalize/whitelist the user-facing option before calling (map 'mp4'→'video-social', 'animated gif'→'gif')","Add switch/case validation at the CLI boundary so invalid kinds never reach this function"],"exampleFix":"// before\nawait downloadRenderedVideo(page, id, i, 'mp4', outDir);\n// after\nconst kind = format === 'gif' ? 'gif' : 'video-social';\nawait downloadRenderedVideo(page, id, i, kind, outDir);","handlingStrategy":"validation","validationCode":"const RENDER_KINDS = ['video-social', 'gif'];\nif (!RENDER_KINDS.includes(kind)) {\n  throw new TypeError(`kind must be one of ${RENDER_KINDS.join(', ')}, got '${kind}'`);\n}\nawait downloadRenderedVideo(page, jobId, index, kind, outputDir);","typeGuard":"function isRenderVideoKind(kind) {\n  return kind === 'video-social' || kind === 'gif';\n}","tryCatchPattern":"try {\n  await downloadRenderedVideo(page, jobId, i, kind, outDir);\n} catch (err) {\n  if (err.name === 'ArgumentError' && err.message.includes('video-social or gif')) {\n    console.error(`Bad kind '${kind}' — use 'video-social' or 'gif'`);\n    process.exitCode = 2;\n  } else throw err;\n}","preventionTips":["Centralize the kind constants and import them rather than inlining strings","Normalize CLI flags to the canonical kinds at the argument-parsing boundary","Add a unit test covering every user-facing format option mapping to a valid kind"],"tags":["argument-error","validation","midjourney"],"backgroundTag":"invalid-argument-value","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}