jackwener/OpenCLI · error · CommandExecutionError

Midjourney did not expose "${config.label}" for video ${jobI

Error message

Midjourney did not expose "${config.label}" for video ${jobId}

What it means

A CommandExecutionError thrown when the injected in-page script cannot find a visible button whose trimmed text exactly equals the expected menu label (e.g. 'Download for Social' or 'Download as GIF') on the Midjourney video page. The library marks the matching menu item before clicking it, so absence of the button means Midjourney's UI does not offer that export for this video.

Source

Thrown at clis/midjourney/utils.js:753

  if (!config) throw new ArgumentError('Rendered video kind must be video-social or gif');
  await fs.mkdir(outputDir, { recursive: true });
  const filePath = path.join(outputDir, `${jobId}_${index + 1}_${kind.replace('video-', '')}${config.extension}`);
  const existing = await existingMedia(filePath, force, config.mime);
  if (existing) return { index, kind, filePath, bytes: existing.size, url: null, mime: config.mime, cached: true };

  await page.goto(jobUrl(jobId, index));
  await page.wait({ selector: 'video[src]', timeout: 20 });
  await page.click('button[title="Options"]');
  await page.wait(0.3);
  const marked = unwrapEvaluateResult(await page.evaluate((label) => {
    document.querySelectorAll('[data-opencli-video-download]').forEach((node) => node.removeAttribute('data-opencli-video-download'));
    const button = [...document.querySelectorAll('button[role="menuitem"],button')]
      .find((node) => node.textContent?.trim() === label && node.getBoundingClientRect().width > 0);
    if (!button) return false;
    button.setAttribute('data-opencli-video-download', '1');
    return true;
  }, config.label));
  if (!marked) throw new CommandExecutionError(`Midjourney did not expose "${config.label}" for video ${jobId}`);
  await page.click('[data-opencli-video-download="1"]');
  if (typeof page.waitForDownload !== 'function') {
    throw new CommandExecutionError('Browser Bridge download lifecycle support is required for social video/GIF export');
  }
  const downloaded = await page.waitForDownload(jobId, 60_000);
  if (!downloaded?.downloaded || downloaded.state !== 'complete' || !downloaded.filename) {
    throw new CommandExecutionError(`Midjourney ${kind} download did not complete: ${downloaded?.error || downloaded?.state || 'unknown'}`);
  }
  const sourcePath = path.resolve(String(downloaded.filename));
  const sourceStat = await fs.stat(sourcePath).catch(() => null);
  if (!sourceStat?.isFile() || sourceStat.size <= 0) {
    throw new CommandExecutionError(`Browser reported a completed download but the file is missing: ${sourcePath}`);
  }
  const handle = await fs.open(sourcePath, 'r');
  let actualMime;
  try {
    const header = Buffer.alloc(16);
    const { bytesRead } = await handle.read(header, 0, header.length, 0);

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Wait for the video page and its action menu to fully render before calling downloadRenderedVideo
  2. Verify the current Midjourney UI button text and update the label in the config map ('Download for Social' / 'Download as GIF')
  3. Confirm this video job actually supports social/GIF export (some video variants do not)
  4. Update your browser session/region so the UI renders the expected English labels
  5. Add a retry that opens the dropdown/menu first, then re-runs the marking script

Example fix

// before
await page.goto(jobUrl(jobId, index));
const marked = await page.evaluate(markScript, config.label);
// after
await page.goto(jobUrl(jobId, index));
await page.waitForSelector('button[role="menuitem"], button', { timeout: 30_000 });
const marked = await page.evaluate(markScript, config.label);
if (!marked) throw new CommandExecutionError(`Midjourney did not expose "${config.label}" for video ${jobId}`);
Defensive patterns

Strategy: retry

Validate before calling

await page.goto(jobUrl(jobId, index), { waitUntil: 'networkidle' });
const hasButton = await page.evaluate(() =>
  [...document.querySelectorAll('button')].some((b) =>
    /Download (for Social|as GIF)/.test(b.textContent?.trim() || '') && b.getBoundingClientRect().width > 0));
if (!hasButton) throw new Error('Midjourney download menu not rendered yet — aborting before export');

Try / catch

try {
  await downloadRenderedVideo(page, jobId, i, kind, outDir);
} catch (err) {
  if (err.message.includes('did not expose')) {
    await page.reload({ waitUntil: 'networkidle' });
    await downloadRenderedVideo(page, jobId, i, kind, outDir);
  } else throw err;
}

Prevention

When it happens

Trigger: page.evaluate marking script runs on the job page and returns false because no visible button matches config.label — Midjourney's dropdown has different text, the menu wasn't opened, or the video has no such export option.

Common situations: Midjourney shipped a UI update renaming or relocating the download menu items; the job is still processing or is a video type without social/GIF export; the page didn't finish loading before evaluate ran; regional/localized UI text differing from English labels.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/fbdb1febdd351e3a. Report an issue: GitHub.