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
- Convert macros: '@daily' -> '0 0 * * *', '@hourly' -> '0 * * * *', '@weekly' -> '0 0 * * 0'
- Replace named days/months with numbers: 'MON-FRI' -> '1-5', 'JAN' -> '1'
- 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
- Expand @-macros and named weekdays/months to numeric POSIX fields before embedding
- Test the regex locally in your editor — it accepts 5 or 6 numeric fields only
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
- bench.scheduleHours must be ≥1
- auditReadView must be an object
- includedEventTypes entries must be non-empty strings
- auditReadView.retentionDays must be ≥1
- roomId may only contain [A-Za-z0-9_.\-:/@#]
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/05de8e7ad4b9371a.
Report an issue: GitHub.