ruvnet/ruflo · error · PodTemplateValidationError

pod-template at ${path}: budgetUsdPerRun must not exceed bud

Error message

pod-template at ${path}: budgetUsdPerRun must not exceed budgetUsdMonthly

What it means

Thrown by validatePodTemplate() when budgetUsdPerRun exceeds budgetUsdMonthly AND budgetUsdMonthly is greater than 0. A single run should never cost more than the entire monthly cap (when a cap is set). When budgetUsdMonthly is 0 (unlimited), this check is skipped. The error message includes the 'pod-template at /:' prefix added by PodTemplateValidationError's constructor.

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 6b01dc5a68)

Solutions

  1. Lower budgetUsdPerRun to be less than or equal to budgetUsdMonthly
  2. Or raise budgetUsdMonthly to accommodate the per-run cost
  3. Or set budgetUsdMonthly to 0 (unlimited) if the monthly cap is intentionally not enforced

Example fix

// before
{ "budgetUsdMonthly": 100, "budgetUsdPerRun": 150 }

// after
{ "budgetUsdMonthly": 500, "budgetUsdPerRun": 150 }
Defensive patterns

Strategy: validation

Validate before calling

if (template.budgetUsdMonthly > 0 && template.budgetUsdPerRun > template.budgetUsdMonthly) {
  throw new Error('budgetUsdPerRun must not exceed budgetUsdMonthly (or set monthly to 0 for unlimited)');
}

Type guard

function budgetsAreConsistent(monthly: number, perRun: number): boolean {
  return monthly === 0 || perRun <= monthly;
}

Try / catch

try {
  validatePodTemplate(json);
} catch (e) {
  if (e instanceof PodTemplateValidationError && e.message.includes('budgetUsdPerRun must not exceed')) {
    // Either raise budgetUsdMonthly or lower budgetUsdPerRun
  }
}

Prevention

When it happens

Trigger: budgetUsdMonthly is a positive number (e.g. 100) and budgetUsdPerRun is larger (e.g. 150). For example, monthly=500, perRun=600 triggers it; monthly=0, perRun=999 does not.

Common situations: Per-run estimate was computed independently from the monthly cap and the two were never reconciled; a decimal point error (e.g. perRun=50.00 vs monthly=5.00).

Related errors


AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12). Data as JSON: /api/errors/68badd1490629452. Report an issue: GitHub.