ruvnet/ruflo · error · Error

invalid-budget-limit

Error message

invalid-budget-limit

What it means

The second gate in setBudget(): periodMs must itself be a finite number, and optional caps maxCostUsd / maxTokens must be finite and non-negative when present. Error('invalid-budget-limit'). This is distinct from invalid-budget-period, which fires first for periodMs <= 0 — this one catches Infinity periodMs and bad cap values.

Source

Thrown at v3/@claude-flow/security/src/policy/engine.ts:99

      rule.constraints?.maxCostUsd,
      rule.constraints?.maxTokens,
      rule.constraints?.maxConcurrency,
    ]) {
      if (value !== undefined && (!Number.isFinite(value) || value < 0)) {
        throw new Error('invalid-policy-rule-limit');
      }
    }
    const index = this.state.rules.findIndex((item) => item.id === rule.id);
    if (index >= 0) this.state.rules[index] = structuredClone(rule);
    else this.state.rules.push(structuredClone(rule));
  }

  setBudget(limit: BudgetLimit): void {
    if (limit.periodMs <= 0) throw new Error('invalid-budget-period');
    if (!Number.isFinite(limit.periodMs)
      || [limit.maxCostUsd, limit.maxTokens].some((value) => (
        value !== undefined && (!Number.isFinite(value) || value < 0)
      ))) throw new Error('invalid-budget-limit');
    const index = this.state.budgets.findIndex((item) => item.id === limit.id);
    if (index >= 0) this.state.budgets[index] = structuredClone(limit);
    else this.state.budgets.push(structuredClone(limit));
  }

  issueApproval(approval: Omit<PolicyApproval, 'uses' | 'issuedAt'> & { uses?: number; issuedAt?: number }): PolicyApproval {
    if (approval.issuedBy === approval.principal) throw new Error('self-approval-forbidden');
    if (this.approvalIssuerVerifier?.(approval.issuedBy) !== true) {
      throw new Error('untrusted-approval-issuer');
    }
    const issuedAt = approval.issuedAt ?? this.now();
    const record: PolicyApproval = { ...approval, issuedAt, uses: approval.uses ?? 0 };
    if (this.state.approvals.some((item) => item.id === record.id)) throw new Error('duplicate-approval-id');
    if (!record.id
      || record.expiresAt <= issuedAt
      || !Number.isInteger(record.maxUses)
      || record.maxUses <= 0
      || !Number.isInteger(record.uses)

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Omit maxCostUsd/maxTokens (leave undefined) when there is no cap for that dimension.
  2. Sanitize parsed numbers at config load: keep only finite, non-negative values, else undefined.
  3. Ensure periodMs is a finite positive integer of milliseconds.

Example fix

// before
engine.setBudget({ id: 'b', periodMs: 3600_000, maxCostUsd: Number('') }); // NaN throws

// after
const cap = Number(env.MAX_COST);
engine.setBudget({
  id: 'b',
  periodMs: 3600_000,
  maxCostUsd: Number.isFinite(cap) && cap >= 0 ? cap : undefined,
});
Defensive patterns

Strategy: validation

Validate before calling

const cap = (v: number | undefined) =>
  v === undefined ? undefined : Number.isFinite(v) && v >= 0 ? v : undefined;
engine.setBudget({
  id,
  periodMs: 3600_000,
  maxCostUsd: cap(rawCost),
  maxTokens: cap(rawTokens),
});

Type guard

function isSanitizedBudget(b: BudgetLimit): boolean {
  const caps = [b.maxCostUsd, b.maxTokens].filter((v) => v !== undefined);
  return Number.isFinite(b.periodMs) && caps.every((v) => Number.isFinite(v as number) && (v as number) >= 0);
}

Try / catch

try {
  engine.setBudget(limit);
} catch (err) {
  if (err instanceof Error && err.message === 'invalid-budget-limit') {
    throw new ConfigError(`budget ${limit.id}: caps must be finite and >= 0`);
  }
  throw err;
}

Prevention

When it happens

Trigger: periodMs: Infinity (e.g. from a bad 'never resets' computation); maxCostUsd: NaN parsed from an unset env var; maxTokens: -100 from inverted arithmetic; maxCostUsd: Infinity intended as 'unlimited'.

Common situations: Env-parsed caps with unset variables; expressing 'unlimited' with Infinity or negative sentinels; copy-pasting limits between systems with different conventions.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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