{"record":{"id":"59f6cdd88646c14a","repo":"jackwener/OpenCLI","slug":"midjourney-budget-guard-would-leave-number-rema","errorCode":null,"errorMessage":"Midjourney budget guard would leave ${Number((remainingMinutes - estimatedMinutes).toFixed(2))} minutes, below the ${reserve}-minute reserve","messagePattern":"Midjourney budget guard would leave (.+?) minutes, below the (.+?)-minute reserve","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/midjourney/capabilities.js","lineNumber":284,"sourceCode":"    effectiveModel: selectedModel,\n    routingReason,\n    resolution,\n    speed,\n    repeat,\n    estimatedMinutes,\n  };\n}\n\nexport function assertBudget(account, estimatedMinutes, maxMinutes, reserveMinutes, creditsToMinutes) {\n  const max = normalizeNonNegativeNumber(maxMinutes, 2, '--max-minutes');\n  const reserve = normalizeNonNegativeNumber(reserveMinutes, 0, '--reserve-minutes');\n  if (estimatedMinutes > max) {\n    throw new ArgumentError(`Estimated GPU cost ${estimatedMinutes} minutes exceeds --max-minutes ${max}`);\n  }\n  const remainingCredits = Number(account?.total_credits ?? account?.credits_total);\n  const remainingMinutes = creditsToMinutes(remainingCredits);\n  if (Number.isFinite(remainingMinutes) && remainingMinutes - estimatedMinutes < reserve) {\n    throw new CommandExecutionError(\n      `Midjourney budget guard would leave ${Number((remainingMinutes - estimatedMinutes).toFixed(2))} minutes, below the ${reserve}-minute reserve`,\n    );\n  }\n  return { maxMinutes: max, reserveMinutes: reserve, remainingMinutes };\n}\n\nexport function estimateAction(\n  operation,\n  { videoResolution = 'sd', batchSize = 1, sourceIsVideo = false, sourceUsesOmni = false } = {},\n) {\n  if (!ACTION_CHOICES.includes(operation)) {\n    throw new ArgumentError(`operation must be one of: ${ACTION_CHOICES.join(', ')}`);\n  }\n  if (operation === 'open-editor' || operation === 'cancel') return 0;\n  if (operation === 'rerun' && sourceIsVideo) {\n    if (!['sd', 'hd'].includes(videoResolution)) throw new ArgumentError('--video-resolution must be sd or hd');\n    if (![1, 2, 4].includes(batchSize)) throw new ArgumentError('--batch-size must be 1, 2, or 4');\n    return GPU_COST_MINUTES[`video${videoResolution === 'hd' ? 'Hd' : 'Sd'}${batchSize}`];","sourceCodeStart":266,"sourceCodeEnd":302,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/midjourney/capabilities.js#L266-L302","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Wait for the subscription's GPU minutes to reset or purchase more Fast GPU time, then retry.","Lower --reserve-minutes if the configured headroom is stricter than you need (e.g. --reserve-minutes 0).","Reduce estimated cost: fewer repeats, drop turbo/hd/omni, or use relax speed (0 GPU minutes).","Check remaining GPU minutes via the account endpoint before submitting and skip the job if remaining - estimate < reserve."],"exampleFix":"// before\nmidjourney generate 'a cat --r 4' --reserve-minutes 10  // only 6 minutes left\n// after\nmidjourney generate 'a cat --r 2' --reserve-minutes 2","handlingStrategy":"try-catch","validationCode":"const account = await midjourney.account();\nconst remainingMinutes = account.gpuMinutesAvailable; // or creditsToMinutes(total_credits)\nconst reserve = Number(args.reserveMinutes ?? 0);\nif (remainingMinutes - estimatedMinutes < reserve) {\n  throw new Error(`would leave ${remainingMinutes - estimatedMinutes} min, below ${reserve}-min reserve; top up or lower reserve`);\n}","typeGuard":"function hasHeadroom(account, estimatedMinutes, reserve, creditsToMinutes) {\n  const credits = Number(account?.total_credits ?? account?.credits_total);\n  const remaining = creditsToMinutes(credits);\n  return Number.isFinite(remaining) && remaining - estimatedMinutes >= reserve;\n}","tryCatchPattern":"try {\n  return await generate(args);\n} catch (err) {\n  if (err instanceof CommandExecutionError && err.message.includes('budget guard would leave')) {\n    const left = Number(err.message.match(/would leave ([\\d.]+)/)[1]);\n    const reserve = Number(err.message.match(/below the ([\\d.]+)-minute/)[1]);\n    if (left < 0) throw new Error('insufficient GPU minutes; waiting for reset');\n    return generate({ ...args, repeat: 1, reserveMinutes: Math.max(0, reserve - 1) });\n  }\n  throw err;\n}","preventionTips":["Check remaining GPU minutes via the account endpoint before every submission.","Set --reserve-minutes intentionally (0 if headroom is not needed), not as an arbitrary large number.","Prefer relax speed near the end of a billing cycle; it costs 0 fast GPU minutes.","Wrap automated batches so they abort early instead of draining the balance below the reserve."],"tags":["cli","budget","quota","gpu-credits","command-execution-error"],"backgroundTag":"insufficient-quota","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}