{"record":{"id":"80fede46f9d4e370","repo":"jackwener/OpenCLI","slug":"midjourney-job-jobid-last-status-laststatus","errorCode":null,"errorMessage":"Midjourney job ${jobId} (last status: ${lastStatus})","messagePattern":"Midjourney job (.+?) \\(last status: (.+?)\\)","errorType":"exception","errorClass":"TimeoutError","httpStatus":null,"severity":"warning","filePath":"clis/midjourney/utils.js","lineNumber":526,"sourceCode":"  }\n  throw new TimeoutError('Midjourney derived job submission', timeoutSeconds, `No new child job appeared for ${parentJobId}.`);\n}\n\nexport async function waitForCompletedJob(page, jobId, timeoutSeconds) {\n  const deadline = Date.now() + timeoutSeconds * 1000;\n  let lastStatus = 'unknown';\n  do {\n    const job = await fetchJobStatus(page, jobId, { allowMissing: true });\n    if (job) {\n      lastStatus = String(job.current_status || job.status || 'unknown').toLowerCase();\n      if (lastStatus === 'completed') return job;\n      if (['failed', 'cancelled', 'canceled', 'error'].includes(lastStatus)) {\n        throw new CommandExecutionError(`Midjourney job ${jobId} ended with status \"${lastStatus}\"`);\n      }\n    }\n    if (!(await waitForNextPoll(page, deadline, 2))) break;\n  } while (true);\n  throw new TimeoutError(\n    `Midjourney job ${jobId} (last status: ${lastStatus})`,\n    timeoutSeconds,\n    `Check the job at ${jobUrl(jobId)} and retry status or download later.`,\n  );\n}\n\nasync function fetchMediaThroughPage(page, url, expectedMimePrefix) {\n  const transferKey = `opencli_media_${Date.now()}_${Math.random().toString(36).slice(2)}`;\n  try {\n    let payload;\n    try {\n      payload = unwrapEvaluateResult(await page.evaluate(async (mediaUrl, key) => {\n        // CDN is public but Cloudflare-protected. Browser-origin fetch succeeds\n        // with default same-origin credential mode; forcing cross-origin cookies\n        // turns it into a credentialed CORS request and Midjourney rejects it.\n        const response = await fetch(mediaUrl);\n        if (!response.ok) return { ok: false, status: response.status, type: response.headers.get('content-type') || '' };\n        const bytes = new Uint8Array(await response.arrayBuffer());","sourceCodeStart":508,"sourceCodeEnd":544,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/midjourney/utils.js#L508-L544","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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).","Increase the timeout (timeoutSeconds) used by the command and retry.","Verify the jobId is correct via the job URL printed in the error.","Check Midjourney service status / queue congestion before retrying."],"exampleFix":"// before\nawait waitAndDownload(page, jobId, { timeoutSeconds: 60 });\n// after\nawait waitAndDownload(page, jobId, { timeoutSeconds: 300 });","handlingStrategy":"retry","validationCode":"const statuses = ['failed','cancelled','canceled','error'];\nif (statuses.includes(lastStatus)) throw new Error('Job already ended: ' + lastStatus);","typeGuard":null,"tryCatchPattern":"try {\n  await waitForJob(page, jobId, { timeoutSeconds: 300 });\n} catch (err) {\n  if (err instanceof TimeoutError) {\n    console.warn('Job ' + jobId + ' still pending; retrying later');\n    await sleep(retryDelay); // re-run status/download later\n  } else throw err;\n}","preventionTips":["Set a generous timeoutSeconds matched to job type (videos/upscalers need more).","Poll job status separately before downloading so completed jobs are handled immediately.","Surface the job URL to users so they can check progress manually.","Back off during known Midjourney congestion periods."],"tags":["timeout","polling","midjourney","async"],"backgroundTag":"operation-timeout","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}