jackwener/OpenCLI · error · CommandExecutionError

Midjourney budget guard would leave ${Number((remainingMinut

Error message

Midjourney budget guard would leave ${Number((remainingMinutes - estimatedMinutes).toFixed(2))} minutes, below the ${reserve}-minute reserve

What it means

assertBudget also enforces a --reserve-minutes floor (default 0) on the account's remaining Fast GPU minutes. This CommandExecutionError is thrown when spending estimatedMinutes would leave the balance below the reserve, computed from account.total_credits/credits_total via creditsToMinutes. Unlike 2598 it is a runtime account-state guard, not an argument validation.

Source

Thrown at clis/midjourney/capabilities.js:284

    effectiveModel: selectedModel,
    routingReason,
    resolution,
    speed,
    repeat,
    estimatedMinutes,
  };
}

export function assertBudget(account, estimatedMinutes, maxMinutes, reserveMinutes, creditsToMinutes) {
  const max = normalizeNonNegativeNumber(maxMinutes, 2, '--max-minutes');
  const reserve = normalizeNonNegativeNumber(reserveMinutes, 0, '--reserve-minutes');
  if (estimatedMinutes > max) {
    throw new ArgumentError(`Estimated GPU cost ${estimatedMinutes} minutes exceeds --max-minutes ${max}`);
  }
  const remainingCredits = Number(account?.total_credits ?? account?.credits_total);
  const remainingMinutes = creditsToMinutes(remainingCredits);
  if (Number.isFinite(remainingMinutes) && remainingMinutes - estimatedMinutes < reserve) {
    throw new CommandExecutionError(
      `Midjourney budget guard would leave ${Number((remainingMinutes - estimatedMinutes).toFixed(2))} minutes, below the ${reserve}-minute reserve`,
    );
  }
  return { maxMinutes: max, reserveMinutes: reserve, remainingMinutes };
}

export function estimateAction(
  operation,
  { videoResolution = 'sd', batchSize = 1, sourceIsVideo = false, sourceUsesOmni = false } = {},
) {
  if (!ACTION_CHOICES.includes(operation)) {
    throw new ArgumentError(`operation must be one of: ${ACTION_CHOICES.join(', ')}`);
  }
  if (operation === 'open-editor' || operation === 'cancel') return 0;
  if (operation === 'rerun' && sourceIsVideo) {
    if (!['sd', 'hd'].includes(videoResolution)) throw new ArgumentError('--video-resolution must be sd or hd');
    if (![1, 2, 4].includes(batchSize)) throw new ArgumentError('--batch-size must be 1, 2, or 4');
    return GPU_COST_MINUTES[`video${videoResolution === 'hd' ? 'Hd' : 'Sd'}${batchSize}`];

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Wait for the subscription's GPU minutes to reset or purchase more Fast GPU time, then retry.
  2. Lower --reserve-minutes if the configured headroom is stricter than you need (e.g. --reserve-minutes 0).
  3. Reduce estimated cost: fewer repeats, drop turbo/hd/omni, or use relax speed (0 GPU minutes).
  4. Check remaining GPU minutes via the account endpoint before submitting and skip the job if remaining - estimate < reserve.

Example fix

// before
midjourney generate 'a cat --r 4' --reserve-minutes 10  // only 6 minutes left
// after
midjourney generate 'a cat --r 2' --reserve-minutes 2
Defensive patterns

Strategy: try-catch

Validate before calling

const account = await midjourney.account();
const remainingMinutes = account.gpuMinutesAvailable; // or creditsToMinutes(total_credits)
const reserve = Number(args.reserveMinutes ?? 0);
if (remainingMinutes - estimatedMinutes < reserve) {
  throw new Error(`would leave ${remainingMinutes - estimatedMinutes} min, below ${reserve}-min reserve; top up or lower reserve`);
}

Type guard

function hasHeadroom(account, estimatedMinutes, reserve, creditsToMinutes) {
  const credits = Number(account?.total_credits ?? account?.credits_total);
  const remaining = creditsToMinutes(credits);
  return Number.isFinite(remaining) && remaining - estimatedMinutes >= reserve;
}

Try / catch

try {
  return await generate(args);
} catch (err) {
  if (err instanceof CommandExecutionError && err.message.includes('budget guard would leave')) {
    const left = Number(err.message.match(/would leave ([\d.]+)/)[1]);
    const reserve = Number(err.message.match(/below the ([\d.]+)-minute/)[1]);
    if (left < 0) throw new Error('insufficient GPU minutes; waiting for reset');
    return generate({ ...args, repeat: 1, reserveMinutes: Math.max(0, reserve - 1) });
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling the generate flow when remainingMinutes - estimatedMinutes < reserveMinutes: e.g. account has 3 GPU minutes left, job estimates 1.5, and --reserve-minutes 2; or reserve set equal to/above the whole balance.

Common situations: Accounts nearly out of Fast GPU time at end of billing cycle; scripts setting a conservative --reserve-minutes to keep headroom for interactive use; running large --repeat batches that drain the balance below the configured floor.

Related errors


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