JuliusBrussee/caveman · error · Error
cave_budget_release_exceeds_max
cave_budget_release_exceeds_max
Error message
cave_budget_release_exceeds_max
What it means
Staged release can top up tranches only up to the run's hard max: an amount greater than releasable() (max − released) throws at the release site, both for BudgetController.releaseBudget and the exhaustion handler's { release, reason } answer. max is the contract; a checkpoint asking beyond it is a programming or policy error, not a run outcome.
Source
Thrown at packages/agent/src/budget.ts:338
* pre-flight validation at a developer-controlled checkpoint, so the caller
* asked for something the contract cannot grant.
*/
release(amount: number, reason: string): BudgetTranche {
if (!Number.isFinite(amount) || amount <= 0) {
throw new Error("cave_budget_release_invalid");
}
if (this.denomination === "tokens" && !Number.isSafeInteger(amount)) {
throw new Error("cave_budget_release_invalid");
}
if (typeof reason !== "string" || reason.trim() === "") {
throw new Error("cave_budget_release_reason_required");
}
if (this.revokedFlag) throw new Error("cave_budget_revoked");
// A breached ledger is dead. Releasing into it would record a tranche and
// raise an escalation for money that can never be spent, and would read on
// the receipt as a run that was still being funded after it went past cap.
if (this.breachedFlag) throw new Error("cave_budget_cap_breached");
if (amount > this.releasable()) throw new Error("cave_budget_release_exceeds_max");
this.releasedAmount += amount;
const tranche: BudgetTranche = Object.freeze({
amount,
reason,
atCall: this.callIndex,
});
this.trancheLog.push(tranche);
return tranche;
}
/**
* Hold `amount` against the ledger. Returns `undefined` when it does not fit,
* which is the caller's signal to clamp, compact, or stop — never to proceed.
*/
reserve(amount: number, outputTokenCap: number): BudgetReservation | undefined {
const held = this.hold(amount, outputTokenCap);
if (held !== undefined) this.callIndex++;
return held;View on GitHub (pinned to 766dce6b13)
Solutions
- Clamp the top-up: Math.min(wanted, releasable) using context.releasable from BudgetExhaustionContext or controller.max - controller.released
- If the plan genuinely needs more, set a higher maxUsd/maxTokens up front
- Return "stop" from the handler when releasable is 0
Example fix
// before
return { release: 1, reason: "top-up" };
// after
const wanted = Math.min(1, ctx.releasable);
return wanted > 0 ? { release: wanted, reason: "top-up" } : "stop"; Defensive patterns
Strategy: validation
Validate before calling
// At a checkpoint, before releasing:
const releasable = controller.max - controller.released;
const topUp = Math.min(wanted, releasable);
if (topUp > 0) controller.releaseBudget(topUp, "phase 2");
// Inside onBudgetExhausted, prefer the context value:
async function onBudgetExhausted(ctx: BudgetExhaustionContext) {
const release = Math.min(1, ctx.releasable);
return release > 0 ? { release, reason: "top-up" } : "stop";
} Try / catch
try {
controller.releaseBudget(amount, reason);
} catch (error) {
if (error instanceof Error && error.message === "cave_budget_release_exceeds_max") {
// amount exceeded max - released; clamp to the true headroom or raise max on a new run
} else throw error;
} Prevention
- Always compute top-ups as Math.min(wanted, releasable) — never a constant
- Size maxUsd/maxTokens for the whole plan up front; staged release only paces spending, it cannot extend the cap
- Return "stop" when releasable is 0 rather than retrying the release
When it happens
Trigger: budget: { maxUsd: 5, initialUsd: 4 } then releaseBudget(2, …) when only 1 is releasable; an exhaustion handler returning a constant { release: 1 } on every escalation; several checkpoints each releasing max-sized tranches.
Common situations: Fixed top-up amounts that ignore how much was already released; retrying the same release after partial success; multiple exhaustion escalations within one run.
Related errors
- cave_budget_initial_invalid
- cave_budget_release_invalid
- cave_budget_release_reason_required
- cave_budget_denomination_ambiguous
- cave_budget_max_invalid
AI-assisted analysis of JuliusBrussee/caveman@766dce6b13 (2026-08-18).
Data as JSON: /api/errors/e61b63e941b67b5f.
Report an issue: GitHub.