jackwener/OpenCLI · error · CommandExecutionError

Midjourney job-status endpoint returned a malformed payload

Error message

Midjourney job-status endpoint returned a malformed payload

What it means

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.

Source

Thrown at clis/midjourney/utils.js:343

  if (!payload || typeof payload !== 'object' || !Array.isArray(payload.data)) {
    throw new CommandExecutionError('Midjourney history endpoint returned a malformed payload');
  }
  return {
    data: payload.data,
    cursor: stringOrNull(payload.cursor),
    checkpoint: stringOrNull(payload.checkpoint),
  };
}

export async function fetchJobStatuses(page, jobIds) {
  if (!Array.isArray(jobIds) || !jobIds.length) return [];
  const payload = await midjourneyJson(page, '/api/job-status', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: { jobIds, _frontend_source: 'opencli_adapter' },
  });
  if (!Array.isArray(payload)) {
    throw new CommandExecutionError('Midjourney job-status endpoint returned a malformed payload');
  }
  return payload;
}

export async function fetchJobStatus(page, jobId, { allowMissing = false } = {}) {
  const payload = await fetchJobStatuses(page, [jobId]);
  const job = payload.find((row) => row?.id === jobId) || null;
  if (!job && !allowMissing) {
    throw new EmptyResultError('midjourney status', `Job ${jobId} was not found in the current account.`);
  }
  return job;
}

export async function cancelMidjourneyJob(page, jobId) {
  let result;
  try {
    result = unwrapEvaluateResult(await page.evaluate(async (id) => {
      const response = await fetch('/api/job-cancel', {

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Log into Midjourney in the driven Chrome session and verify the page can load midjourney.com (most non-array responses are auth/HTML interstitials).
  2. Call fetchJobStatuses again after a short delay — transient Cloudflare/rate-limit responses often resolve on retry.
  3. Check whether Midjourney changed the /api/job-status response shape and update the adapter to unwrap the new envelope.
  4. Inspect the raw response (add logging in midjourneyJson) to confirm what the endpoint actually returned.

Example fix

// before
const payload = await midjourneyJson(page, '/api/job-status', {...});
if (!Array.isArray(payload)) {
  throw new CommandExecutionError('Midjourney job-status endpoint returned a malformed payload');
}
// after
let payload = await midjourneyJson(page, '/api/job-status', {...});
if (payload && Array.isArray(payload.jobs)) payload = payload.jobs; // unwrap envelope if API changed
if (!Array.isArray(payload)) {
  throw new CommandExecutionError('Midjourney job-status endpoint returned a malformed payload');
}
Defensive patterns

Strategy: type-guard

Validate before calling

// before calling: confirm session can reach the API surface
const page = await getPage();
if (!page.url().includes('midjourney.com')) throw new Error('Open a midjourney.com page first');
if (!(await isLoggedInToMidjourney(page))) throw new Error('Log into Midjourney before fetching job statuses');

Type guard

function isJobStatusPayload(v) {
  return Array.isArray(v) && v.every((row) => row == null || typeof row === 'object');
}
// usage: only trust the result when isJobStatusPayload(payload) holds

Try / catch

try {
  const jobs = await fetchJobStatuses(page, jobIds);
} catch (e) {
  if (String(e.message).includes('malformed payload')) {
    // likely auth/HTML interstitial: refresh session and retry once
    await page.reload({ waitUntil: 'networkidle' });
    const jobs = await fetchJobStatuses(page, jobIds);
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


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