ruvnet/ruflo · error · PodTemplateValidationError

budgetUsdMonthly must be ≥0

Error message

budgetUsdMonthly must be ≥0

What it means

The top-level budgetUsdMonthly number must be >= 0. Negative budgets are meaningless; zero is allowed and means 'no monthly cap' (note: the cross-field per-run check in error 193 only applies when budgetUsdMonthly > 0). requireNumber already rejected non-numbers, so this error is specifically a negative numeric value.

Source

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

        tp,
      );
    }
    return t;
  });
  if (allowedMcpTools.length === 0) {
    throw new PodTemplateValidationError('allowedMcpTools must have ≥1 entry', '/');
  }
  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)',
      '/',
    );

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Use 0 to express 'no monthly budget' — it is the schema's intended unlimited/no-cap value
  2. Fix sign errors: any positive USD amount is valid
  3. If porting configs that use -1 for unlimited, translate -1 -> 0 in your template converter

Example fix

// before
"budgetUsdMonthly": -1
// after
"budgetUsdMonthly": 0
Defensive patterns

Strategy: validation

Validate before calling

if (typeof json.budgetUsdMonthly !== 'number' || json.budgetUsdMonthly < 0) {
  json.budgetUsdMonthly = Math.max(0, Number(json.budgetUsdMonthly) || 0);
}

Type guard

function isValidBudget(v: unknown): v is number {
  return typeof v === 'number' && Number.isFinite(v) && v >= 0;
}

Try / catch

try { validatePodTemplate(json); } catch (err) {
  if (err instanceof PodTemplateValidationError && /budgetUsdMonthly/.test(err.message)) {
    // map -1 'unlimited' sentinels to 0 and re-validate
  }
}

Prevention

When it happens

Trigger: A template with budgetUsdMonthly set to a negative number, e.g. -1 or -100. A string like "-5" would instead fail requireNumber with a missing/invalid-field error.

Common situations: Using -1 as a 'no budget / unlimited' sentinel the way other config systems do; sign typos; spreadsheet exports producing negative placeholder values.

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


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