jackwener/OpenCLI · warning · TimeoutError

Midjourney job ${jobId} (last status: ${lastStatus})

Error message

Midjourney job ${jobId} (last status: ${lastStatus})

What it means

A Midjourney job did not reach a terminal success state within the configured timeoutSeconds, so the polling loop exits and this TimeoutError is thrown. The message carries the jobId and the last observed status so you know how far the job got. It is thrown deliberately after the poll deadline passes, unlike the CommandExecutionError thrown when the status is explicitly failed/cancelled.

Source

Thrown at clis/midjourney/utils.js:526

  }
  throw new TimeoutError('Midjourney derived job submission', timeoutSeconds, `No new child job appeared for ${parentJobId}.`);
}

export async function waitForCompletedJob(page, jobId, timeoutSeconds) {
  const deadline = Date.now() + timeoutSeconds * 1000;
  let lastStatus = 'unknown';
  do {
    const job = await fetchJobStatus(page, jobId, { allowMissing: true });
    if (job) {
      lastStatus = String(job.current_status || job.status || 'unknown').toLowerCase();
      if (lastStatus === 'completed') return job;
      if (['failed', 'cancelled', 'canceled', 'error'].includes(lastStatus)) {
        throw new CommandExecutionError(`Midjourney job ${jobId} ended with status "${lastStatus}"`);
      }
    }
    if (!(await waitForNextPoll(page, deadline, 2))) break;
  } while (true);
  throw new TimeoutError(
    `Midjourney job ${jobId} (last status: ${lastStatus})`,
    timeoutSeconds,
    `Check the job at ${jobUrl(jobId)} and retry status or download later.`,
  );
}

async function fetchMediaThroughPage(page, url, expectedMimePrefix) {
  const transferKey = `opencli_media_${Date.now()}_${Math.random().toString(36).slice(2)}`;
  try {
    let payload;
    try {
      payload = unwrapEvaluateResult(await page.evaluate(async (mediaUrl, key) => {
        // CDN is public but Cloudflare-protected. Browser-origin fetch succeeds
        // with default same-origin credential mode; forcing cross-origin cookies
        // turns it into a credentialed CORS request and Midjourney rejects it.
        const response = await fetch(mediaUrl);
        if (!response.ok) return { ok: false, status: response.status, type: response.headers.get('content-type') || '' };
        const bytes = new Uint8Array(await response.arrayBuffer());

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Re-run the status or download command later; the job may still complete on Midjourney's side (the error hint says to check the job URL).
  2. Increase the timeout (timeoutSeconds) used by the command and retry.
  3. Verify the jobId is correct via the job URL printed in the error.
  4. Check Midjourney service status / queue congestion before retrying.

Example fix

// before
await waitAndDownload(page, jobId, { timeoutSeconds: 60 });
// after
await waitAndDownload(page, jobId, { timeoutSeconds: 300 });
Defensive patterns

Strategy: retry

Validate before calling

const statuses = ['failed','cancelled','canceled','error'];
if (statuses.includes(lastStatus)) throw new Error('Job already ended: ' + lastStatus);

Try / catch

try {
  await waitForJob(page, jobId, { timeoutSeconds: 300 });
} catch (err) {
  if (err instanceof TimeoutError) {
    console.warn('Job ' + jobId + ' still pending; retrying later');
    await sleep(retryDelay); // re-run status/download later
  } else throw err;
}

Prevention

When it happens

Trigger: Calling a status/wait command for a job whose generation takes longer than the timeout (e.g. slow queue, upscale, or video render); polling a jobId that is stuck in a non-terminal status like 'processing' or 'queued' until the deadline expires.

Common situations: Midjourney service congestion during peak hours; too-low timeoutSeconds configured by the caller; network flakiness slowing the browser-bridge polls; very large image grids or long videos exceeding default timeouts.

Related errors


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