JuliusBrussee/caveman · error
cave_budget_initial_invalid
cave_budget_initial_invalid
Error message
cave_budget_initial_invalid
What it means
normalizeRunBudget validates the starting budget: initial (initialUsd/initialTokens, defaulting to max) must be a finite positive number not exceeding max, and for the tokens denomination must be a safe integer. An initial greater than the cap, negative, zero, NaN, or fractional token initial throws cave_budget_initial_invalid.
Solutions
- Clamp initial to [0 < initial <= max] and, for tokens, Math.floor it to an integer.
- If the intent is 'start with the full cap', omit the initial field entirely (defaults to max).
- Validate the computed remaining/initial value with isFinite and bounds checks before building the budget.
Example fix
// before
budget({ maxUsd: 1, initialUsd: 5 })
// after
budget({ maxUsd: 1, initialUsd: 0.75 }) Defensive patterns
Strategy: validation
Validate before calling
function validInitial(b) {
const max = b.maxUsd ?? b.maxTokens;
const initial = (b.maxUsd !== undefined ? b.initialUsd : b.initialTokens) ?? max;
return Number.isFinite(initial) && initial > 0 && initial <= max &&
(b.maxUsd !== undefined || Number.isSafeInteger(initial));
}
if (!validInitial(budget)) throw new Error('initial must be > 0, <= max, integer for tokens'); Type guard
const inRange = (v: number, max: number): boolean => Number.isFinite(v) && v > 0 && v <= max;
Try / catch
try { normalizeRunBudget(b); } catch (e) {
if (e.message === 'cave_budget_initial_invalid') {
const max = b.maxUsd ?? b.maxTokens;
b = { ...b, [b.maxUsd !== undefined ? 'initialUsd' : 'initialTokens']: Math.min(max, Math.max(0.01, Math.floor(max))) };
} else throw e;
} Prevention
- Clamp computed remaining/initial values to (0, max] before constructing the budget.
- Floor fractional token initials; omit initial to default to max.
- Validate resume-state numbers (finite, non-negative) before feeding them into a new run.
When it happens
Trigger: Passing { maxUsd: 1, initialUsd: 5 } (initial > max), { maxTokens: 10000, initialTokens: 500.5 } (fractional), { initialUsd: 0 } / negative values, or an initial that evaluates to NaN from a bad config parse.
Common situations: Resuming a run with a 'remaining' value computed elsewhere that exceeds the configured cap; dividing remaining tokens across sub-runs yielding fractions; sign errors when subtracting already-spent amounts (negative initial).
Understand the failure class
Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.
Related errors
- cave_breaker_retry_requires_budget
- cave_budget_denomination_ambiguous
- cave_budget_max_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/b3c61dc999c7288d.
Report an issue: GitHub.
Appendix: source
Thrown at packages/agent/src/budget.ts:105
* 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";
if (onExhausted !== "compact" && onExhausted !== "stop") {
throw new Error("cave_budget_on_exhausted_invalid");
}
return Object.freeze({
denomination,
max,
initial,
outputFloorTokens,
onExhausted,View on GitHub (pinned to 3ee70a1026)