{"record":{"id":"d8fed21320a6009e","repo":"nanocoai/nanoclaw","slug":"invalid-recurrence-msg","errorCode":null,"errorMessage":"invalid --recurrence: ${msg}","messagePattern":"invalid --recurrence: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/modules/scheduling/create.ts","lineNumber":73,"sourceCode":"  const hex = (n: number): string => randomUUID().replace(/-/g, '').slice(0, n);\n  const slug = taskNameSlug(name);\n  return slug ? `${slug}-${hex(4)}` : `t-${hex(6)}`;\n}\n\nexport function parseProcessAfter(value: unknown, tz: string = TIMEZONE): string {\n  if (typeof value !== 'string' || value.length === 0) throw new Error('--process-after is required');\n  const date = parseZonedToUtc(value, tz);\n  if (Number.isNaN(date.getTime())) throw new Error(`invalid --process-after: ${value}`);\n  return date.toISOString();\n}\n\nexport function validateRecurrence(value: string | null | undefined, tz: string = TIMEZONE): void {\n  if (!value) return;\n  try {\n    CronExpressionParser.parse(value, { tz });\n  } catch (err) {\n    const msg = err instanceof Error ? err.message : String(err);\n    throw new Error(`invalid --recurrence: ${msg}`, { cause: err });\n  }\n}\n\nexport function enforceRecurrenceLimit(\n  recurrence: string | null,\n  override: boolean,\n  hasScript: boolean,\n  tz: string = TIMEZONE,\n): void {\n  // A gate script is the sanctioned mitigation: a skipped fire costs no agent\n  // tokens, so scripted tasks may poll faster without the explicit override.\n  if (!recurrence || override || hasScript) return;\n  const horizon = Date.now() + 24 * 60 * 60 * 1000;\n  const interval = CronExpressionParser.parse(recurrence, { tz });\n  let fires = 0;\n  while (fires <= MAX_DAILY_FIRES) {\n    const next = interval.next();\n    if (next.getTime() > horizon) break;","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/nanocoai/nanoclaw/blob/294ef2aee85218b23ad30eda9dfe10e590b54a8c/src/modules/scheduling/create.ts#L55-L91","documentation":"Thrown when the --recurrence value passed to validateRecurrence fails to parse as a valid cron expression via CronExpressionParser (cron-parser). The original parser message is embedded so the user sees exactly which cron field is invalid. It is thrown before any task is created, acting as input validation for scheduled task recurrence.","triggerScenarios":"Calling prepareScheduledTask or updateTaskCommand with a recurrence string that is not valid cron syntax (e.g. 'every day', '@daily' if unsupported by the parser version, '0 0 * *' with too few fields), or with a 5-field cron when the parser is configured for 6-field, or an invalid timezone making the expression unparseable in that tz.","commonSituations":"Users coming from systemd timers, ISO 8601 durations (R/PT1H), or @-shorthand cron who assume the scheduler accepts them; copy-pasting a 5-field crontab entry into a tool expecting 6 fields; typos like '0 0 * * * * *' (too many fields) or '* * */e * *'.","solutions":["Fix the recurrence string to be a valid cron expression for cron-parser (typically 5 or 6 fields: '0 9 * * 1-5' for weekdays at 9am).","Verify expression validity in a scratch script: import { CronExpressionParser } from 'cron-parser' and parse it directly to see the raw parser error.","If you meant an ISO duration or 'every N minutes' style, convert to cron ('*/15 * * * *' for every 15 minutes).","Check that the timezone argument is a valid IANA name — an invalid tz can surface as a parse failure."],"exampleFix":"// before\nawait prepareScheduledTask({ prompt: 'daily report', recurrence: 'every day at 9' });\n\n// after\nawait prepareScheduledTask({ prompt: 'daily report', recurrence: '0 9 * * *' });","handlingStrategy":"validation","validationCode":"import { CronExpressionParser } from 'cron-parser';\nfunction isValidCron(expr: string, tz?: string): boolean {\n  try { CronExpressionParser.parse(expr, { tz }); return true; } catch { return false; }\n}","typeGuard":"const isValidCron = (e: string, tz?: string): boolean => { try { CronExpressionParser.parse(e, { tz }); return true; } catch { return false; } };","tryCatchPattern":"try { validateRecurrence(recurrence, tz); } catch (err) { throw new Error(`Bad --recurrence \"${recurrence}\": ${(err as Error).message}`, { cause: err }); }","preventionTips":["Validate cron strings at the config/CLI boundary, not at task creation.","Keep a lint rule or test asserting all shipped recurrence strings parse.","Document the expected cron field count for your cron-parser version."],"tags":["cron","scheduling","input-validation","tasks"],"backgroundTag":"invalid-cron-expression","analyzedSha":"294ef2aee85218b23ad30eda9dfe10e590b54a8c","analyzedAt":"2026-08-28T13:59:10.357Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}