jackwener/OpenCLI · error · CommandExecutionError
Midjourney job ${jobId} ended with status "${lastStatus}"
Error message
Midjourney job ${jobId} ended with status "${lastStatus}" What it means
waitForCompletedJob polls the job until it reaches 'completed'. If the job instead lands in a terminal failure state ('failed', 'cancelled', 'canceled', 'error'), the loop stops and throws CommandExecutionError reporting the job ID and final status. This surfaces Midjourney-side job failure promptly rather than waiting for timeout.
Source
Thrown at clis/midjourney/utils.js:521
if (candidates.length > 1) {
throw new CommandExecutionError(
`Midjourney action is ambiguous; ${candidates.length} derived jobs matched parent ${parentJobId}`,
candidates.map((row) => jobUrl(row.id)).join(', '),
);
}
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 succeedsView on GitHub (pinned to 49907e53dc)
Solutions
- Read lastStatus in the message and inspect the job on midjourney.com for the failure reason.
- If the prompt was moderated/rejected, revise the prompt and submit a new job.
- If cancelled, resubmit — do not wait on the old ID.
- For transient 'error' status, resubmit the same job/prompt; these often succeed on retry.
Example fix
// before
const job = await waitForCompletedJob(page, jobId, 120);
// after
let job;
try {
job = await waitForCompletedJob(page, jobId, 120);
} catch (e) {
if (/ended with status "(failed|error)"/.test(String(e.message))) {
job = await resubmitAndWait(page, originalPrompt); // transient failure: resubmit
} else throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
// check the job's current status before entering a long wait
const job = await fetchJobStatus(page, jobId, { allowMissing: true });
const st = String(job?.current_status || job?.status || '').toLowerCase();
if (['failed', 'cancelled', 'canceled', 'error'].includes(st)) {
console.warn(`Job ${jobId} is already ${st}; skipping wait.`);
} Type guard
function isTerminalFailure(job) {
const st = String(job?.current_status || job?.status || '').toLowerCase();
return ['failed', 'cancelled', 'canceled', 'error'].includes(st);
} Try / catch
try {
const job = await waitForCompletedJob(page, jobId, 120);
} catch (e) {
const m = String(e.message).match(/ended with status "(\w+)"/);
if (m && ['failed', 'error'].includes(m[1])) {
// transient failure: resubmit the prompt as a new job
} else if (m && ['cancelled', 'canceled'].includes(m[1])) {
// user- or system-cancelled: do not retry, surface to the user
} else throw e;
} Prevention
- Review prompts against Midjourney content policy before submission to avoid moderation failures
- Do not cancel jobs in the web UI while CLI automation waits on them
- Persist final job status so waits are skipped for known-terminal jobs
- Treat 'error' as retryable and 'failed'/'cancelled' as not, when automating retries
When it happens
Trigger: The awaited job transitions to failed/cancelled/canceled/error on Midjourney's side: content-policy rejection, moderation block, user cancelled via web UI, or an internal generation error.
Common situations: Prompts violating Midjourney's content policy get auto-failed; users cancelling jobs from the web while the CLI waits; Midjourney service errors failing a batch of jobs; jobs failing after parameter errors (bad aspect/seed combos).
Related errors
- Midjourney submission is ambiguous; ${ambiguousIds.length} n
- Midjourney job submission
- Midjourney job ${jobId} (last status: ${lastStatus})
- Timeout waiting for Antigravity reply after ${timeout / 1000
- Waiting for Bilibili session cookies
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/4c5558190c8462a5.
Report an issue: GitHub.