ruvnet/ruflo · error · PodTemplateValidationError

budgetUsdPerRun must not exceed budgetUsdMonthly

Error message

budgetUsdPerRun must not exceed budgetUsdMonthly

What it means

This is the raw constructor argument for the same budgetUsdPerRun > budgetUsdMonthly check. The string 'budgetUsdPerRun must not exceed budgetUsdMonthly' is the first argument to 'new PodTemplateValidationError(message, path)' before the constructor prepends 'pod-template at ${path}: '. A developer inspecting the throw site or the raw message field sees this form; the constructed Error.message includes the prefix.

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

Solutions

  1. Ensure budgetUsdPerRun <= budgetUsdMonthly when budgetUsdMonthly > 0
  2. Set budgetUsdMonthly to 0 to disable the monthly cap entirely
  3. Match on 'PodTemplateValidationError' name rather than the raw message string in catch blocks

Example fix

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

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

Strategy: validation

Validate before calling

const monthly = template.budgetUsdMonthly;
const perRun = template.budgetUsdPerRun;
if (monthly > 0 && perRun > monthly) {
  throw new Error('budgetUsdPerRun must not exceed budgetUsdMonthly');
}

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 'pod-template at /: budgetUsdPerRun must not exceed budgetUsdMonthly'
    // Match on e.name === 'PodTemplateValidationError' rather than raw message
  }
}

Prevention

When it happens

Trigger: budgetUsdMonthly > 0 && budgetUsdPerRun > budgetUsdMonthly. Same logical condition as the constructed error — the raw message is what the throw statement passes to the PodTemplateValidationError constructor.

Common situations: Same as the constructed form: per-run budget exceeds monthly cap when a monthly cap is active (>0). Encountered when inspecting source or matching against the raw message string in error-handling code that reads the constructor argument rather than .message.

Related errors


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