JuliusBrussee/caveman · error · Error
cave_budget_on_exhausted_invalid
Error message
cave_budget_on_exhausted_invalid
What it means
Thrown by normalizeRunBudget when the onExhausted policy is anything other than the two supported strings "compact" or "stop". This field controls what the runtime does when the budget runs out — compact the context and continue cheaper, or stop the run — and an unknown value cannot be safely guessed, so it fails closed. The field is optional with a default of "compact".
Source
Thrown at packages/agent/src/budget.ts:116
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,
compaction: normalizeCompaction(budget.compaction),
});
}
/** Identity and shape of one prospective provider call. */
export interface CallCeiling {
readonly provider: string;
readonly model: string;
/** Upper bound on the request's input tokens. Never an estimate that can be low. */
readonly inputTokenCeiling: number;
/** The output allowance this call would ask for. */View on GitHub (pinned to 27d5a3981a)
Solutions
- Use exactly "compact" or "stop" (lowercase), or omit the field to accept the "compact" default.
- Type the config field in your own code as a union type so the compiler rejects bad values before runtime.
- If the value comes from user input, normalize it: lowercase and trim before validation, and reject unknown values with a helpful message.
Example fix
// before
const budget = { maxUsd: 5, onExhausted: "abort" as any };
// after
type OnExhausted = "compact" | "stop";
const raw = String(process.env.ON_EXHAUSTED ?? "").toLowerCase().trim();
const budget = { maxUsd: 5, ...(raw === "compact" || raw === "stop" ? { onExhausted: raw satisfies OnExhausted } : {}) }; Defensive patterns
Strategy: type-guard
Validate before calling
const raw = String(process.env.ON_EXHAUSTED ?? "").trim().toLowerCase(); const onExhausted = raw === "stop" ? "stop" : "compact"; // unknown values fall back to default
Type guard
function isOnExhausted(v: unknown): v is "compact" | "stop" {
return v === "compact" || v === "stop";
} Prevention
- Type your own config field as the literal union "compact" | "stop" so the compiler rejects typos.
- Normalize case (lowercase, trim) on user-supplied values before validation.
- Omit the field to accept the "compact" default rather than restating it in many places.
When it happens
Trigger: Passing onExhausted: "abort", "halt", "continue", or any string outside the union; passing a non-string (number, boolean, object); passing a string with different casing like "Stop" or "COMPACT".
Common situations: Config files written from memory instead of the docs, using plausible-but-unsupported verbs like "abort" or "fail"; case mismatches after a config-linting tool normalizes enum-like values to uppercase; version drift if a newer release renamed the policy values and the config was not migrated.
Related errors
- cave_budget_denomination_ambiguous
- cave_budget_max_invalid
- cave_budget_output_floor_invalid
- cave_budget_initial_invalid
- cave_budget_release_invalid
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/ac48c34afaa6ff68.
Report an issue: GitHub.