{"record":{"id":"a8267bbc7d4466a0","repo":"jackwener/OpenCLI","slug":"browser-bridge-download-lifecycle-support-is-requi","errorCode":null,"errorMessage":"Browser Bridge download lifecycle support is required for social video/GIF export","messagePattern":"Browser Bridge download lifecycle support is required for social video/GIF export","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/midjourney/utils.js","lineNumber":756,"sourceCode":"  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}`);\n  await page.click('[data-opencli-video-download=\"1\"]');\n  if (typeof page.waitForDownload !== 'function') {\n    throw new CommandExecutionError('Browser Bridge download lifecycle support is required for social video/GIF export');\n  }\n  const downloaded = await page.waitForDownload(jobId, 60_000);\n  if (!downloaded?.downloaded || downloaded.state !== 'complete' || !downloaded.filename) {\n    throw new CommandExecutionError(`Midjourney ${kind} download did not complete: ${downloaded?.error || downloaded?.state || 'unknown'}`);\n  }\n  const sourcePath = path.resolve(String(downloaded.filename));\n  const sourceStat = await fs.stat(sourcePath).catch(() => null);\n  if (!sourceStat?.isFile() || sourceStat.size <= 0) {\n    throw new CommandExecutionError(`Browser reported a completed download but the file is missing: ${sourcePath}`);\n  }\n  const handle = await fs.open(sourcePath, 'r');\n  let actualMime;\n  try {\n    const header = Buffer.alloc(16);\n    const { bytesRead } = await handle.read(header, 0, header.length, 0);\n    actualMime = sniffMediaMime(header.subarray(0, bytesRead));\n  } finally {\n    await handle.close();","sourceCodeStart":738,"sourceCodeEnd":774,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/midjourney/utils.js#L738-L774","documentation":"A CommandExecutionError raised when the page object lacks a waitForDownload function, meaning the connected Browser Bridge does not implement the download lifecycle API. The social/GIF export flow relies on the bridge to report when a browser-initiated download completes; without that hook it cannot reliably capture the file.","triggerScenarios":"downloadRenderedVideo clicks the marked download button, then checks typeof page.waitForDownload === 'function' and it is undefined — the bridge client is an older version or a plain Playwright/Puppeteer page without the extension.","commonSituations":"Running an outdated opencli browser-bridge client against a newer CLI (or vice versa); using a self-built bridge that never implemented waitForDownload; pointing the CLI at a browser session not managed by the bridge.","solutions":["Upgrade the Browser Bridge client/extension to a version that implements waitForDownload(jobId, timeoutMs)","Ensure the CLI connects through the official bridge-managed page object rather than a raw Playwright/Puppeteer page","Pin matching versions of the CLI and bridge packages in your environment","If you maintain a custom bridge, implement waitForDownload returning { downloaded, state, filename, mime }"],"exampleFix":"// before\nconst page = await playwright.chromium.launch().newPage();\nawait downloadRenderedVideo(page, jobId, 0, 'gif', outDir);\n// after\nconst page = await bridge.connect({ browser: 'chromium' }); // bridge page implements waitForDownload\nawait downloadRenderedVideo(page, jobId, 0, 'gif', outDir);","handlingStrategy":"type-guard","validationCode":"if (typeof page?.waitForDownload !== 'function') {\n  throw new Error('Browser Bridge lacks waitForDownload — upgrade the bridge client before exporting social video/GIF');\n}\nawait downloadRenderedVideo(page, jobId, i, kind, outDir);","typeGuard":"function supportsDownloadLifecycle(page) {\n  return typeof page?.waitForDownload === 'function';\n}","tryCatchPattern":"try {\n  await downloadRenderedVideo(page, jobId, i, kind, outDir);\n} catch (err) {\n  if (err.message.includes('download lifecycle support')) {\n    console.error('Upgrade your Browser Bridge: npm i -g @opencli/browser-bridge@latest');\n  } else throw err;\n}","preventionTips":["Keep the Browser Bridge client and CLI on matching versions (pin both in CI)","Check for waitForDownload support at startup and fail fast with a clear upgrade message","Avoid passing raw Playwright/Puppeteer pages where a bridge-managed page is expected"],"tags":["browser-bridge","version-compatibility","midjourney","api-support"],"backgroundTag":"unsupported-api-version","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}