nanocoai/nanoclaw · error

invalid --recurrence: ${msg}

Error message

invalid --recurrence: ${msg}

What it means

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.

Source

Thrown at src/modules/scheduling/create.ts:73

  const hex = (n: number): string => randomUUID().replace(/-/g, '').slice(0, n);
  const slug = taskNameSlug(name);
  return slug ? `${slug}-${hex(4)}` : `t-${hex(6)}`;
}

export function parseProcessAfter(value: unknown, tz: string = TIMEZONE): string {
  if (typeof value !== 'string' || value.length === 0) throw new Error('--process-after is required');
  const date = parseZonedToUtc(value, tz);
  if (Number.isNaN(date.getTime())) throw new Error(`invalid --process-after: ${value}`);
  return date.toISOString();
}

export function validateRecurrence(value: string | null | undefined, tz: string = TIMEZONE): void {
  if (!value) return;
  try {
    CronExpressionParser.parse(value, { tz });
  } catch (err) {
    const msg = err instanceof Error ? err.message : String(err);
    throw new Error(`invalid --recurrence: ${msg}`, { cause: err });
  }
}

export function enforceRecurrenceLimit(
  recurrence: string | null,
  override: boolean,
  hasScript: boolean,
  tz: string = TIMEZONE,
): void {
  // A gate script is the sanctioned mitigation: a skipped fire costs no agent
  // tokens, so scripted tasks may poll faster without the explicit override.
  if (!recurrence || override || hasScript) return;
  const horizon = Date.now() + 24 * 60 * 60 * 1000;
  const interval = CronExpressionParser.parse(recurrence, { tz });
  let fires = 0;
  while (fires <= MAX_DAILY_FIRES) {
    const next = interval.next();
    if (next.getTime() > horizon) break;

View on GitHub (pinned to 294ef2aee8)

Solutions

  1. 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).
  2. Verify expression validity in a scratch script: import { CronExpressionParser } from 'cron-parser' and parse it directly to see the raw parser error.
  3. If you meant an ISO duration or 'every N minutes' style, convert to cron ('*/15 * * * *' for every 15 minutes).
  4. Check that the timezone argument is a valid IANA name — an invalid tz can surface as a parse failure.

Example fix

// before
await prepareScheduledTask({ prompt: 'daily report', recurrence: 'every day at 9' });

// after
await prepareScheduledTask({ prompt: 'daily report', recurrence: '0 9 * * *' });
Defensive patterns

Strategy: validation

Validate before calling

import { CronExpressionParser } from 'cron-parser';
function isValidCron(expr: string, tz?: string): boolean {
  try { CronExpressionParser.parse(expr, { tz }); return true; } catch { return false; }
}

Type guard

const isValidCron = (e: string, tz?: string): boolean => { try { CronExpressionParser.parse(e, { tz }); return true; } catch { return false; } };

Try / catch

try { validateRecurrence(recurrence, tz); } catch (err) { throw new Error(`Bad --recurrence "${recurrence}": ${(err as Error).message}`, { cause: err }); }

Prevention

When it happens

Trigger: 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.

Common situations: 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 * *'.

Related errors


AI-assisted analysis of nanocoai/nanoclaw@294ef2aee8 (2026-08-28). Data as JSON: /api/errors/d8fed21320a6009e. Report an issue: GitHub.