{"record":{"id":"20bae25aa9d71de1","repo":"jackwener/OpenCLI","slug":"midjourney-job-status-endpoint-returned-a-malforme","errorCode":null,"errorMessage":"Midjourney job-status endpoint returned a malformed payload","messagePattern":"Midjourney job-status endpoint returned a malformed payload","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/midjourney/utils.js","lineNumber":343,"sourceCode":"  if (!payload || typeof payload !== 'object' || !Array.isArray(payload.data)) {\n    throw new CommandExecutionError('Midjourney history endpoint returned a malformed payload');\n  }\n  return {\n    data: payload.data,\n    cursor: stringOrNull(payload.cursor),\n    checkpoint: stringOrNull(payload.checkpoint),\n  };\n}\n\nexport async function fetchJobStatuses(page, jobIds) {\n  if (!Array.isArray(jobIds) || !jobIds.length) return [];\n  const payload = await midjourneyJson(page, '/api/job-status', {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json' },\n    body: { jobIds, _frontend_source: 'opencli_adapter' },\n  });\n  if (!Array.isArray(payload)) {\n    throw new CommandExecutionError('Midjourney job-status endpoint returned a malformed payload');\n  }\n  return payload;\n}\n\nexport async function fetchJobStatus(page, jobId, { allowMissing = false } = {}) {\n  const payload = await fetchJobStatuses(page, [jobId]);\n  const job = payload.find((row) => row?.id === jobId) || null;\n  if (!job && !allowMissing) {\n    throw new EmptyResultError('midjourney status', `Job ${jobId} was not found in the current account.`);\n  }\n  return job;\n}\n\nexport async function cancelMidjourneyJob(page, jobId) {\n  let result;\n  try {\n    result = unwrapEvaluateResult(await page.evaluate(async (id) => {\n      const response = await fetch('/api/job-cancel', {","sourceCodeStart":325,"sourceCodeEnd":361,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/midjourney/utils.js#L325-L361","documentation":"fetchJobStatuses POSTs job IDs to Midjourney's /api/job-status endpoint and expects the response JSON to be an array of job rows. When the payload parses but is not an array (e.g. an error object, HTML page, or null), the function treats it as unusable and throws CommandExecutionError. This guards callers from undefined behavior when assuming array access on the result.","triggerScenarios":"Calling fetchJobStatus/fetchJobStatuses when the /api/job-status endpoint returns JSON that is not an array: an auth/redirect HTML page parsed as JSON, an error object like {error: ...}, a rate-limit JSON body, or a Midjourney API change that wraps the array in an object.","commonSituations":"Expired or missing Midjourney session cookies causing an HTML login page response; Midjourney API schema changes; Cloudflare or proxy interstitials returning non-array JSON; hitting the endpoint while logged out or rate limited.","solutions":["Log into Midjourney in the driven Chrome session and verify the page can load midjourney.com (most non-array responses are auth/HTML interstitials).","Call fetchJobStatuses again after a short delay — transient Cloudflare/rate-limit responses often resolve on retry.","Check whether Midjourney changed the /api/job-status response shape and update the adapter to unwrap the new envelope.","Inspect the raw response (add logging in midjourneyJson) to confirm what the endpoint actually returned."],"exampleFix":"// before\nconst payload = await midjourneyJson(page, '/api/job-status', {...});\nif (!Array.isArray(payload)) {\n  throw new CommandExecutionError('Midjourney job-status endpoint returned a malformed payload');\n}\n// after\nlet payload = await midjourneyJson(page, '/api/job-status', {...});\nif (payload && Array.isArray(payload.jobs)) payload = payload.jobs; // unwrap envelope if API changed\nif (!Array.isArray(payload)) {\n  throw new CommandExecutionError('Midjourney job-status endpoint returned a malformed payload');\n}","handlingStrategy":"type-guard","validationCode":"// before calling: confirm session can reach the API surface\nconst page = await getPage();\nif (!page.url().includes('midjourney.com')) throw new Error('Open a midjourney.com page first');\nif (!(await isLoggedInToMidjourney(page))) throw new Error('Log into Midjourney before fetching job statuses');","typeGuard":"function isJobStatusPayload(v) {\n  return Array.isArray(v) && v.every((row) => row == null || typeof row === 'object');\n}\n// usage: only trust the result when isJobStatusPayload(payload) holds","tryCatchPattern":"try {\n  const jobs = await fetchJobStatuses(page, jobIds);\n} catch (e) {\n  if (String(e.message).includes('malformed payload')) {\n    // likely auth/HTML interstitial: refresh session and retry once\n    await page.reload({ waitUntil: 'networkidle' });\n    const jobs = await fetchJobStatuses(page, jobIds);\n  } else throw e;\n}","preventionTips":["Keep the Midjourney Chrome session logged in before running status commands","Wrap non-array API responses at one boundary (midjourneyJson) and log raw bodies for debugging","Retry transient payload failures once after a short delay","Watch for Midjourney API shape changes and unwrap new envelopes defensively"],"tags":["midjourney","http-response","schema-validation"],"backgroundTag":"malformed-api-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}