ruvnet/ruflo · error · PodTemplateValidationError

pod-template at /: cronSchedule must be a POSIX cron express

Error message

pod-template at /: cronSchedule must be a POSIX cron expression (5 or 6 fields)

What it means

The top-level cronSchedule string must satisfy CRON_RE = /^([\d*/,\-]+\s+){4,5}[\d*/,\-]+$/ — a POSIX cron expression of five or six space-separated fields containing only digits, *, /, -, and ,. The check is deliberately permissive on field semantics (real evaluation happens at schedule time); it only catches obviously malformed values. The 'pod-template at /:' prefix is the caller's path formatting, not part of the thrown message.

Source

Thrown at v3/@claude-flow/cli/src/business-pods/pod-schema.ts:244

  }
  const budgetUsdMonthly = requireNumber(json, 'budgetUsdMonthly', '/');
  if (budgetUsdMonthly < 0) {
    throw new PodTemplateValidationError('budgetUsdMonthly must be ≥0', '/');
  }
  const budgetUsdPerRun = requireNumber(json, 'budgetUsdPerRun', '/');
  if (budgetUsdPerRun < 0) {
    throw new PodTemplateValidationError('budgetUsdPerRun must be ≥0', '/');
  }
  if (budgetUsdMonthly > 0 && budgetUsdPerRun > budgetUsdMonthly) {
    throw new PodTemplateValidationError(
      'budgetUsdPerRun must not exceed budgetUsdMonthly',
      '/',
    );
  }
  const preferLocalExecution = requireBoolean(json, 'preferLocalExecution', '/');
  const cronSchedule = requireString(json, 'cronSchedule', '/');
  if (!CRON_RE.test(cronSchedule)) {
    throw new PodTemplateValidationError(
      'cronSchedule must be a POSIX cron expression (5 or 6 fields)',
      '/',
    );
  }
  const auditReadView = validateAuditReadView(json.auditReadView, '/auditReadView');

  let reservationExpiryMs: number | undefined;
  if (json.reservationExpiryMs !== undefined) {
    const v = requireNumber(json, 'reservationExpiryMs', '/');
    // ADR-164.1 §3.2 — bounded to [5_000, 300_000] ms.
    if (v < 5_000 || v > 300_000) {
      throw new PodTemplateValidationError(
        'reservationExpiryMs must be within [5000, 300000] ms (ADR-164.1 §3.2)',
        '/',
      );
    }
    reservationExpiryMs = v;
  }

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Convert macros: '@daily' -> '0 0 * * *', '@hourly' -> '0 * * * *', '@weekly' -> '0 0 * * 0'
  2. Replace named days/months with numbers: 'MON-FRI' -> '1-5', 'JAN' -> '1'
  3. Verify locally: /^([\d*/,\-]+\s+){4,5}[\d*/,\-]+$/.test(cronSchedule) before validating the template

Example fix

// before
"cronSchedule": "@daily"
// after
"cronSchedule": "0 0 * * *"
Defensive patterns

Strategy: validation

Validate before calling

const CRON_RE = /^([\d*/,\-]+\s+){4,5}[\d*/,\-]+$/;
if (!CRON_RE.test(json.cronSchedule)) {
  json.cronSchedule = '0 * * * *'; // or convert '@daily' -> '0 0 * * *'
}

Type guard

function isPosixCron(v: unknown): v is string {
  return typeof v === 'string' && /^([\d*/,\-]+\s+){4,5}[\d*/,\-]+$/.test(v);
}

Try / catch

try { validatePodTemplate(json); } catch (err) {
  if (err instanceof PodTemplateValidationError && /cronSchedule/.test(err.message)) {
    // expand macros (@daily, @hourly) and named days (MON->1) into numeric fields
  }
}

Prevention

When it happens

Trigger: cronSchedule set to '@daily' or '@hourly' (Vixie macros — rejected), named days/months like 'MON-FRI' or 'JAN', human text like 'every 6 hours', a 7-field expression (seconds + year), or an empty string. Six-field seconds-first expressions like '0 0 * * * *' do pass.

Common situations: Copying from crontab.guru or systemd timer syntax that uses macros/names; adding a seconds field AND a year field; quoting issues leaving a stray character in the string.

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/05de8e7ad4b9371a. Report an issue: GitHub.