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
- Set the chosen max to a finite positive number; for maxTokens use a whole integer.
- Validate/parse numeric config inputs before constructing the budget (reject NaN/Infinity early).
- 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
- Sanitize numeric env/config inputs: reject NaN and Infinity at parse time.
- Use maxUsd for fractional caps; keep maxTokens an integer.
- Never compute caps with division that can produce Infinity/NaN without guards.
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
- cave_breaker_retry_requires_budget
- cave_budget_denomination_ambiguous
- cave_budget_initial_invalid
- caveman build: maxSearchCostUsd must be positive
- ccr max storage bytes must be positive
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)