{"record":{"id":"6f3f591f240d3574","repo":"jackwener/OpenCLI","slug":"batch-size-must-be-1-2-or-4","errorCode":null,"errorMessage":"--batch-size must be 1, 2, or 4","messagePattern":"--batch-size must be 1, 2, or 4","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/midjourney/capabilities.js","lineNumber":301,"sourceCode":"  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}`];\n  }\n  if (sourceUsesOmni && ['rerun', 'vary-subtle', 'vary-strong'].includes(operation)) {\n    return GPU_COST_MINUTES.omni;\n  }\n  if (Object.prototype.hasOwnProperty.call(ACTION_COSTS, operation)) return ACTION_COSTS[operation];\n  if (['animate-low', 'animate-high', 'loop-low', 'loop-high', 'extend-low', 'extend-high'].includes(operation)) {\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}`];\n  }\n  throw new ArgumentError(`No cost model is defined for action \"${operation}\"`);\n}\n\nexport function assertActionPlan(account, operation, { videoResolution = 'sd' } = {}) {\n  const capabilities = planCapabilities(account);\n  if (videoResolution === 'hd' && !capabilities.canHdVideo\n    && ['animate-low', 'animate-high', 'loop-low', 'loop-high', 'extend-low', 'extend-high'].includes(operation)) {","sourceCodeStart":283,"sourceCodeEnd":319,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/midjourney/capabilities.js#L283-L319","documentation":"For video reruns, estimateAction restricts batchSize to 1, 2, or 4 because GPU cost keys (videoSd1, videoHd2, ...) only exist for those sizes. Any other batchSize (including 0, 3, or non-numbers) throws this ArgumentError.","triggerScenarios":"estimateAction('rerun', { sourceIsVideo: true, batchSize: 3 }) or batchSize as a string '2' from an uncoerced CLI flag.","commonSituations":"Users passing --batch-size 3 (not offered by Midjourney); reading batch size from JSON config where it is a string; arithmetic producing fractional batches.","solutions":["Use batchSize of exactly 1, 2, or 4 (a number, not a string)","Coerce and validate: Number(kwargs.batchSize) and check [1,2,4].includes(n) before the call","Update the CLI flag help text so users know the allowed set","Snap invalid values to the nearest allowed size at the CLI boundary if snapping is acceptable"],"exampleFix":"// before\nestimateAction('rerun', { sourceIsVideo: true, batchSize: '2' });\n// after\nconst n = Number(opts.batchSize);\nif (![1, 2, 4].includes(n)) throw new Error('--batch-size must be 1, 2, or 4');\nestimateAction('rerun', { sourceIsVideo: true, batchSize: n });","handlingStrategy":"validation","validationCode":"const n = Number(opts.batchSize ?? 1);\nif (!Number.isInteger(n) || ![1, 2, 4].includes(n)) {\n  throw new Error(`--batch-size must be 1, 2, or 4, got ${opts.batchSize}`);\n}\nconst minutes = estimateAction('rerun', { sourceIsVideo: true, batchSize: n, ...opts });","typeGuard":"function isValidBatchSize(n) {\n  return typeof n === 'number' && Number.isInteger(n) && [1, 2, 4].includes(n);\n}","tryCatchPattern":"try {\n  const minutes = estimateAction('rerun', { sourceIsVideo: true, batchSize, videoResolution });\n} catch (err) {\n  if (err instanceof ArgumentError && err.message.includes('--batch-size')) {\n    console.error('Allowed batch sizes: 1, 2, 4');\n  } else throw err;\n}","preventionTips":["Always Number()-coerce CLI flags before use","Restrict --batch-size choices in the arg parser to 1/2/4","Default to 1 when unspecified","Never pass string values from config straight into estimateAction"],"tags":["argument-error","validation","cli","batch"],"backgroundTag":"invalid-argument-value","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}