JuliusBrussee/caveman · error

cave_budget_max_invalid

cave_budget_max_invalid

Error message

cave_budget_max_invalid

What it means

After choosing the denomination, normalizeRunBudget validates the chosen cap (maxUsd or maxTokens). It must be a finite number greater than 0; for the tokens denomination it must additionally be a safe integer. Any NaN, Infinity, zero, negative, or fractional token cap throws this sentinel error.

Solutions

  1. Set the chosen max to a finite positive number; for maxTokens use a whole integer.
  2. Validate/parse numeric config inputs before constructing the budget (reject NaN/Infinity early).
  3. Switch to maxUsd if you need a fractional cap.

Example fix

// before
budget({ maxTokens: 1.5 })
// after
budget({ maxTokens: 15000 })
Defensive patterns

Strategy: validation

Validate before calling

function validBudgetMax(b) {
  const max = b.maxUsd ?? b.maxTokens;
  return Number.isFinite(max) && max > 0 && (b.maxUsd !== undefined || Number.isSafeInteger(b.maxTokens));
}
if (!validBudgetMax(budget)) throw new Error('budget max must be finite > 0 (integer for tokens)');

Type guard

const isPositiveFinite = (v: unknown): v is number => typeof v === 'number' && Number.isFinite(v) && v > 0;

Try / catch

try { normalizeRunBudget(b); } catch (e) {
  if (e.message === 'cave_budget_max_invalid') console.error(`invalid budget max: ${b.maxUsd ?? b.maxTokens}`);
  throw e;
}

Prevention

When it happens

Trigger: Passing { maxTokens: 0 }, { maxTokens: -100 }, { maxTokens: 1.5 } (fractional tokens), { maxUsd: NaN } (e.g. from a bad parse), or { maxUsd: Infinity }.

Common situations: Parsing budget numbers from strings/env vars without validation (parseFloat of '' → NaN); computing a cap from a rate formula that divides by zero → Infinity; specifying a dollar-style fractional cap while in the tokens denomination.

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 JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/d97e4f37117cdb9f. Report an issue: GitHub.

Appendix: source

Thrown at packages/agent/src/budget.ts:96

  readonly max: number;
  readonly initial: number;
  readonly outputFloorTokens: number;
  readonly onExhausted: "compact" | "stop";
  readonly compaction: NormalizedCompaction;
}

/**
 * Validate a caller-supplied budget. Fails closed: an ambiguous, unbounded, or
 * self-contradicting budget is rejected before the first provider call rather
 * than silently degrading into no cap at all.
 */
export function normalizeRunBudget(budget: RunBudget): NormalizedBudget {
  const usd = budget.maxUsd !== undefined;
  const tokens = budget.maxTokens !== undefined;
  if (usd === tokens) throw new Error("cave_budget_denomination_ambiguous");
  const denomination: BudgetDenomination = usd ? "usd" : "tokens";
  const max = usd ? budget.maxUsd! : budget.maxTokens!;
  if (!Number.isFinite(max) || max <= 0) throw new Error("cave_budget_max_invalid");
  if (denomination === "tokens" && !Number.isSafeInteger(max)) {
    throw new Error("cave_budget_max_invalid");
  }
  const wrongInitial = denomination === "usd" ? budget.initialTokens : budget.initialUsd;
  if (wrongInitial !== undefined) throw new Error("cave_budget_denomination_ambiguous");
  const declaredInitial = denomination === "usd" ? budget.initialUsd : budget.initialTokens;
  const initial = declaredInitial ?? max;
  if (!Number.isFinite(initial) || initial <= 0 || initial > max) {
    throw new Error("cave_budget_initial_invalid");
  }
  if (denomination === "tokens" && !Number.isSafeInteger(initial)) {
    throw new Error("cave_budget_initial_invalid");
  }
  const outputFloorTokens = budget.outputFloorTokens ?? OUTPUT_CLAMP_FLOOR_TOKENS;
  if (!Number.isSafeInteger(outputFloorTokens) || outputFloorTokens <= 0) {
    throw new Error("cave_budget_output_floor_invalid");
  }
  const onExhausted = budget.onExhausted ?? "compact";

View on GitHub (pinned to 3ee70a1026)