{"record":{"id":"8c841ffda0f13b13","repo":"aaif-goose/goose","slug":"expected-5-or-6-fields","errorCode":null,"errorMessage":"Expected 5 or 6 fields","messagePattern":"Expected 5 or 6 fields","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"ui/desktop/src/utils/cronSchedule.ts","lineNumber":90,"sourceCode":"  }\n  const parsedDay = parseInt(value, 10);\n  if (parsedDay < 1 || parsedDay > max) {\n    return null;\n  }\n  return parsedDay.toString();\n};\n\nconst asCustomCron = (parts: string[]): ParsedCron => {\n  const [second, minute, hour, dayOfMonth, month, dayOfWeek] = parts;\n  return { period: 'custom', second, minute, hour, dayOfMonth, month, dayOfWeek };\n};\n\nexport const describeCron = (cron: string): string => {\n  const parts = cron.trim().split(/\\s+/);\n  if (parts.length === 5 || parts.length === 6) {\n    return cronstrue.toString(parts.join(' '));\n  }\n  throw new Error('Expected 5 or 6 fields');\n};\n\nexport const parseCron = (cron: string): ParsedCron => {\n  if (!cron.trim()) {\n    return defaultParsedCron;\n  }\n\n  const parts = normalizeCronParts(cron);\n  if (!parts) {\n    return { ...defaultParsedCron, period: 'custom' };\n  }\n\n  const [second, minute, hour, dayOfMonth, month, dayOfWeek] = parts;\n\n  if (!isSingleNumericValue(second)) {\n    return asCustomCron(parts);\n  }\n","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/aaif-goose/goose/blob/3810898a7447ec3299be72e223d3570a7aabf0ab/ui/desktop/src/utils/cronSchedule.ts#L72-L108","documentation":"describeCron() splits the cron expression on whitespace and only hands 5-field (standard) or 6-field (with seconds) expressions to cronstrue. Any other field count — 4, 7+, or an empty-ish string that still splits to the wrong count — throws 'Expected 5 or 6 fields' before cronstrue ever runs. Note parseCron() is more forgiving (it returns a 'custom' period via normalizeCronParts returning null), so this is specific to the human-readable description path.","triggerScenarios":"Calling describeCron('0 0 * * * * *') (7 fields, e.g. Quartz with year); describeCron('0 12 * *') (missing a field); describeCron('@daily') (named schedules are not expanded here); double-space strings are fine (split on \\s+) but stray tokens like trailing words break the count.","commonSituations":"Schedules UI receiving user-typed cron; migrated data containing Quartz 7-field expressions; copy-paste from tutorials with @hourly macros; schedulers that prefix or append extra tokens (timezone names like 'UTC' as a 6th/7th word).","solutions":["Normalize the expression to exactly 5 fields (minute hour dom month dow) or 6 with a leading seconds field before calling describeCron","Expand named schedules (@daily, @hourly) to their field form first","Strip appended timezone/extra tokens before passing the string","Prefer parseCron()+field-level rendering for arbitrary user input; it never throws this error"],"exampleFix":"// before\ndescribeCron('0 0 12 * * ? 2026'); // 7 fields -> throws\n// after\ndescribeCron('0 12 * * ?'); // 6 fields (sec min hour dom mon dow)","handlingStrategy":"type-guard","validationCode":"const fields = cron.trim().split(/\\s+/).filter(Boolean);\nif (fields.length !== 5 && fields.length !== 6) {\n  // reject/normalize input before describeCron\n  return 'Invalid schedule';\n}","typeGuard":"const isFiveOrSixFieldCron = (cron: string): boolean => {\n  const n = cron.trim().split(/\\s+/).filter(Boolean).length;\n  return n === 5 || n === 6;\n};","tryCatchPattern":"try {\n  label = describeCron(cron);\n} catch (e) {\n  if (e instanceof Error && e.message === 'Expected 5 or 6 fields') {\n    label = cron; // show raw expression instead of crashing the settings UI\n  } else throw e;\n}","preventionTips":["Validate field count at the form input, not at render time","Convert @daily/@hourly macros to fields before describing","Reuse parseCron for user input; it degrades to 'custom' instead of throwing"],"tags":["cron","scheduling","validation","typescript"],"backgroundTag":null,"analyzedSha":"3810898a7447ec3299be72e223d3570a7aabf0ab","analyzedAt":"2026-08-16T10:14:26.282Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}