{"record":{"id":"70f39be093070f49","repo":"nanocoai/nanoclaw","slug":"recurrence-has-no-upcoming-run-recurrence","errorCode":null,"errorMessage":"--recurrence has no upcoming run: ${recurrence}","messagePattern":"--recurrence has no upcoming run: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/modules/scheduling/create.ts","lineNumber":122,"sourceCode":"  name?: string;\n  prompt: string;\n  recurrence?: string | null;\n  processAfter?: string;\n  script?: string | null;\n  dangerouslyOverrideRecurrenceLimit?: boolean;\n  timezone?: string;\n}): PreparedScheduledTask {\n  if (!input.prompt) throw new Error('--prompt is required');\n  const recurrence = input.recurrence ?? null;\n  const script = input.script ?? null;\n  const tz = input.timezone ?? TIMEZONE;\n  validateRecurrence(recurrence, tz);\n  enforceRecurrenceLimit(recurrence, input.dangerouslyOverrideRecurrenceLimit === true, script !== null, tz);\n\n  let processAfter: string;\n  if (input.processAfter === undefined && recurrence) {\n    const next = CronExpressionParser.parse(recurrence, { tz }).next().toISOString();\n    if (!next) throw new Error(`--recurrence has no upcoming run: ${recurrence}`);\n    processAfter = next;\n  } else {\n    processAfter = parseProcessAfter(input.processAfter, tz);\n  }\n\n  return { name: input.name, prompt: input.prompt, recurrence, script, processAfter };\n}\n\n/** Persist a prepared task through NanoClaw's single task/session representation. */\nexport async function createScheduledTask(\n  agentGroupId: string,\n  task: PreparedScheduledTask,\n  options?: { status?: 'pending' | 'paused'; originSessionId?: string | null },\n): Promise<{ session: { id: string; agent_group_id: string }; row: ScheduledTaskRow }> {\n  const id = makeTaskId(task.name);\n  const { session } = await resolveTaskSession(agentGroupId, id);\n\n  const row = await withMailboxSession(agentGroupId, session.id, async (db) => {","sourceCodeStart":104,"sourceCodeEnd":140,"githubUrl":"https://github.com/nanocoai/nanoclaw/blob/294ef2aee85218b23ad30eda9dfe10e590b54a8c/src/modules/scheduling/create.ts#L104-L140","documentation":"For a recurring task with no explicit processAfter, the scheduler computes the first run as the cron expression's next execution time. If cron-parser's next() returns a falsy value (no future match), the recurrence can never fire, so the task is rejected with '--recurrence has no upcoming run'. This typically happens for expressions whose only matches are in the past or that are inherently unsatisfiable.","triggerScenarios":"Passing a cron expression with a bounded/past-only component, e.g. a specific past date field, or a constraint cron-parser evaluates as having no next occurrence (certain day-of-month/day-of-week contradictions or year fields in the past). Recurrence is set and processAfter is undefined, so the next() branch is taken.","commonSituations":"Hand-written crons with impossible field combinations (e.g. '0 0 31 2 *' — Feb 31); expressions carrying a past year; migrating from a system that accepted never-matching expressions silently.","solutions":["Test the expression directly: CronExpressionParser.parse(recurrence, { tz }).next() in a scratch script to confirm it yields a date.","Fix impossible field combinations (day-of-month/day-of-week/year) so at least one future instant matches.","If you want the task to start at a specific time regardless, pass processAfter explicitly and let recurrence govern subsequent runs.","If the recurrence is genuinely one-shot-in-the-past, drop recurrence and use processAfter alone."],"exampleFix":"// before\nawait prepareScheduledTask({ prompt: 'x', recurrence: '0 0 31 2 *' });\n\n// after\nawait prepareScheduledTask({ prompt: 'x', recurrence: '0 0 1 * *' });","handlingStrategy":"validation","validationCode":"import { CronExpressionParser } from 'cron-parser';\nconst hasNextRun = (expr: string, tz?: string): boolean => { try { return CronExpressionParser.parse(expr, { tz }).next() !== undefined; } catch { return false; } };","typeGuard":null,"tryCatchPattern":"try { await prepareScheduledTask(input); } catch (err) { if ((err as Error).message.includes('no upcoming run')) throw new Error(`Recurrence never fires: ${input.recurrence}`, { cause: err }); throw err; }","preventionTips":["Sanity-check next() in a dry-run before scheduling.","Avoid exotic cron fields (year, contradictory DOM/DOW) unless proven to fire."],"tags":["cron","scheduling","tasks"],"backgroundTag":"cron-no-future-run","analyzedSha":"294ef2aee85218b23ad30eda9dfe10e590b54a8c","analyzedAt":"2026-08-28T13:59:10.357Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}