ruvnet/ruflo · error · PodTemplateValidationError
pod-template at /: budgetUsdPerRun must not exceed budgetUsd
Error message
pod-template at /: budgetUsdPerRun must not exceed budgetUsdMonthly
What it means
A cross-field budget sanity check in validatePodTemplate(): when budgetUsdMonthly > 0 (a monthly cap exists) and budgetUsdPerRun exceeds it, the template is rejected — a single run could never legally execute under the monthly cap. The 'pod-template at /:' prefix seen in logs is the caller (business_pod_validate / pod-tick) formatting the thrown message with its JSON-pointer path; the thrown message itself is 'budgetUsdPerRun must not exceed budgetUsdMonthly'.
Source
Thrown at v3/@claude-flow/cli/src/business-pods/pod-schema.ts:236
}
const bench = validatePodBench(json.bench, '/bench');
const piiPolicy = requireString(json, 'piiPolicy', '/');
if (!PII_POLICIES.includes(piiPolicy as PiiPolicy)) {
throw new PodTemplateValidationError(
`piiPolicy must be one of: ${PII_POLICIES.join(', ')}`,
'/',
);
}
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.View on GitHub (pinned to fa13ee4ad6)
Solutions
- Raise budgetUsdMonthly to at least budgetUsdPerRun (realistically per-run * expected runs)
- Lower budgetUsdPerRun to fit within the monthly cap
- Set budgetUsdMonthly to 0 only if you genuinely want no monthly cap (this disables the cross-check)
Example fix
// before "budgetUsdMonthly": 10, "budgetUsdPerRun": 50 // after "budgetUsdMonthly": 500, "budgetUsdPerRun": 50
Defensive patterns
Strategy: validation
Validate before calling
const { budgetUsdMonthly: m, budgetUsdPerRun: r } = json;
if (typeof m === 'number' && typeof r === 'number' && m > 0 && r > m) {
throw new Error(`per-run budget ${r} exceeds monthly cap ${m}`);
} Type guard
function budgetsAreConsistent(m: unknown, r: unknown): boolean {
return !(typeof m === 'number' && typeof r === 'number' && m > 0 && r > m);
} Try / catch
try { validatePodTemplate(json); } catch (err) {
if (err instanceof PodTemplateValidationError && /must not exceed/.test(err.message)) {
// either raise the monthly cap or lower per-run; monthly=0 disables the check
}
} Prevention
- Derive budgetUsdPerRun from budgetUsdMonthly / expected-runs in your generator
- Double-check unit consistency (dollars vs cents) between the two fields
When it happens
Trigger: budgetUsdMonthly: 10 with budgetUsdPerRun: 50 — one run would blow the monthly cap 5x. Both values individually pass their >= 0 checks; only the relation fails. Note budgetUsdMonthly: 0 disables this check entirely.
Common situations: Units mismatch (monthly in cents, per-run in dollars); fields swapped when filling the template; per-run cost estimates from a pricier model pasted into a template with a small monthly cap.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- budgetUsdMonthly must be ≥0
- budgetUsdPerRun must be ≥0
- bench.scheduleHours must be ≥1
- auditReadView must be an object
- includedEventTypes entries must be non-empty strings
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/1a46d92f588a8923.
Report an issue: GitHub.