{"record":{"id":"a8420977114aeca8","repo":"jlcodes99/cockpit-tools","slug":"crontab-step-must-be-positive","errorCode":"crontab_step_must_be_positive","errorMessage":"crontab_step_must_be_positive","messagePattern":"crontab_step_must_be_positive","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/pages/WakeupTasksPage.tsx","lineNumber":482,"sourceCode":"  normalizeDayOfWeek: boolean,\n) => {\n  for (let value = start; value <= end; value += step) {\n    target.add(normalizeDayOfWeek ? normalizeCrontabDayOfWeek(value) : value);\n  }\n};\n\nconst parseCrontabSegment = (\n  segment: string,\n  min: number,\n  max: number,\n  normalizeDayOfWeek: boolean,\n  target: Set<number>,\n) => {\n  const [rawRange, rawStep] = segment.split('/');\n  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;","sourceCodeStart":464,"sourceCodeEnd":500,"githubUrl":"https://github.com/jlcodes99/cockpit-tools/blob/1ed8b77992d62ca81fabf744deb0839ad361d5bf/src/pages/WakeupTasksPage.tsx#L464-L500","documentation":"parseCrontabSegment parses a single crontab segment like '*/5' or '1-10/2'. When a step ('/' suffix) is present it is parsed with parseCrontabNumber; if the resulting step is <= 0 the field cannot produce a valid progression, so the parser throws 'crontab_step_must_be_positive'.","triggerScenarios":"Calling parseCrontabSegment (indirectly via parseCrontabField/parseCrontabExpression, e.g. from the Wakeup Tasks crontab input) with a segment whose step is 0 or negative, such as '*/0', '*/-5', '1-10/0'.","commonSituations":"Users typing a crontab in the WakeupTasksPage editor and entering 0 or a negative number after '/'; programmatic generation of cron strings where the step is computed (e.g. interval/0 when interval defaults to 0).","solutions":["Fix the crontab expression so the step after '/' is a positive integer (e.g. '*/5' instead of '*/0').","If the step is computed, clamp it: Math.max(1, step) before building the expression.","Validate the crontab input with parseCrontabExpression in a try/catch before saving, showing the raw code to the user."],"exampleFix":"// before\nconst expr = `*/${intervalMinutes}`; // intervalMinutes = 0\n// after\nconst step = Math.max(1, Math.floor(intervalMinutes || 1));\nconst expr = `*/${step}`;","handlingStrategy":"validation","validationCode":"function validateCrontabExpression(expr) {\n  try { parseCrontabExpression(expr); return true; } catch { return false; }\n}\n// or directly:\nconst step = Number(rawStep);\nif (!Number.isInteger(step) || step <= 0) throw new Error('crontab_step_must_be_positive');","typeGuard":"const isValidCrontabStep = (s: unknown): s is number =>\n  typeof s === 'number' && Number.isInteger(s) && s > 0;","tryCatchPattern":"try {\n  const parsed = parseCrontabExpression(expr);\n} catch (e) {\n  if ((e as Error).message === 'crontab_step_must_be_positive') {\n    setFieldError('step must be a positive integer, e.g. */5');\n  }\n}","preventionTips":["Never build '*/N' segments with computed N without clamping N >= 1","Run parseCrontabExpression as a form-level validator before saving any crontab","Show the raw error code next to the crontab input so users see which field is wrong"],"tags":["validation","crontab","input-parsing"],"backgroundTag":"crontab-step-must-be-positive","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"}