{"record":{"id":"8bd750eb14e061be","repo":"jlcodes99/cockpit-tools","slug":"crontab-range-invalid","errorCode":"crontab_range_invalid","errorMessage":"crontab_range_invalid","messagePattern":"crontab_range_invalid","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/pages/WakeupTasksPage.tsx","lineNumber":497,"sourceCode":"  const rangePart = rawRange.trim();\n  const step = rawStep ? parseCrontabNumber(rawStep) : 1;\n  if (step <= 0) {\n    throw new Error('crontab_step_must_be_positive');\n  }\n\n  if (rangePart === '*') {\n    insertCrontabRange(target, min, max, step, normalizeDayOfWeek);\n    return;\n  }\n\n  if (rangePart.includes('-')) {\n    const [rawStart, rawEnd] = rangePart.split('-');\n    const start = parseCrontabNumber(rawStart);\n    const end = parseCrontabNumber(rawEnd);\n    validateCrontabValue(start, min, max, normalizeDayOfWeek);\n    validateCrontabValue(end, min, max, normalizeDayOfWeek);\n    if (end < start) {\n      throw new Error('crontab_range_invalid');\n    }\n    insertCrontabRange(target, start, end, step, normalizeDayOfWeek);\n    return;\n  }\n\n  const single = parseCrontabNumber(rangePart);\n  validateCrontabValue(single, min, max, normalizeDayOfWeek);\n  if (step === 1) {\n    target.add(normalizeDayOfWeek ? normalizeCrontabDayOfWeek(single) : single);\n    return;\n  }\n  insertCrontabRange(target, single, max, step, normalizeDayOfWeek);\n};\n\nconst parseCrontabField = (\n  field: string,\n  min: number,\n  max: number,","sourceCodeStart":479,"sourceCodeEnd":515,"githubUrl":"https://github.com/jlcodes99/cockpit-tools/blob/1ed8b77992d62ca81fabf744deb0839ad361d5bf/src/pages/WakeupTasksPage.tsx#L479-L515","documentation":"When a crontab segment contains an explicit range 'start-end', both bounds are validated against the field's min/max, then compared. If end < start the range is descending and cannot be expanded, so parseCrontabSegment throws 'crontab_range_invalid'.","triggerScenarios":"A segment like '10-5' in the minute field, or any 'start-end/step' segment where the numeric end is less than the start (both already passing validateCrontabValue bounds).","commonSituations":"Hand-typed cron expressions in the Wakeup Tasks UI where the user swaps bounds ('30-10' meaning 'every 10 from 30'); copy-pasted cron from docs using a different dialect that permits wrap-around ranges.","solutions":["Rewrite the range so start <= end (e.g. '5-10' instead of '10-5').","If a wrap-around range is intended, express it as two ranges (e.g. '22-23,0-5' instead of '22-5').","Catch the error at input time and surface 'crontab_range_invalid' as an inline form validation message before submit."],"exampleFix":"// before\n'22-5 * * * *'   // end < start\n// after\n'22-23,0-5 * * * *'  // or '5-22 * * * *'","handlingStrategy":"validation","validationCode":"const m = segment.match(/^(\\d+)-(\\d+)/);\nif (m && Number(m[2]) < Number(m[1])) {\n  throw new Error('crontab_range_invalid');\n}","typeGuard":"const isAscendingRange = (start: number, end: number): boolean => end >= start;","tryCatchPattern":"try {\n  const parsed = parseCrontabExpression(expr);\n} catch (e) {\n  if ((e as Error).message === 'crontab_range_invalid') {\n    setFieldError('range end must be >= start; use a comma list for wrap-around');\n  }\n}","preventionTips":["When generating ranges programmatically, sort bounds: [Math.min(a,b), Math.max(a,b)]","Express wrap-around windows as two ranges joined by commas","Validate cron input inline as the user types, not only on submit"],"tags":["validation","crontab","input-parsing"],"backgroundTag":"crontab-range-invalid","analyzedSha":"1ed8b77992d62ca81fabf744deb0839ad361d5bf","analyzedAt":"2026-09-05T09:51:41.178Z","contentChangedAt":"2026-09-05T09:51:41.178Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}