{"record":{"id":"7b761f23186ab39e","repo":"JuliusBrussee/caveman","slug":"cave-budget-cap-breached","errorCode":null,"errorMessage":"cave_budget_cap_breached","messagePattern":"cave_budget_cap_breached","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/budget.ts","lineNumber":337,"sourceCode":"   * Release a further tranche. Throws when it would breach `max`: this is\n   * pre-flight validation at a developer-controlled checkpoint, so the caller\n   * asked for something the contract cannot grant.\n   */\n  release(amount: number, reason: string): BudgetTranche {\n    if (!Number.isFinite(amount) || amount <= 0) {\n      throw new Error(\"cave_budget_release_invalid\");\n    }\n    if (this.denomination === \"tokens\" && !Number.isSafeInteger(amount)) {\n      throw new Error(\"cave_budget_release_invalid\");\n    }\n    if (typeof reason !== \"string\" || reason.trim() === \"\") {\n      throw new Error(\"cave_budget_release_reason_required\");\n    }\n    if (this.revokedFlag) throw new Error(\"cave_budget_revoked\");\n    // A breached ledger is dead. Releasing into it would record a tranche and\n    // raise an escalation for money that can never be spent, and would read on\n    // the receipt as a run that was still being funded after it went past cap.\n    if (this.breachedFlag) throw new Error(\"cave_budget_cap_breached\");\n    if (amount > this.releasable()) throw new Error(\"cave_budget_release_exceeds_max\");\n    this.releasedAmount += amount;\n    const tranche: BudgetTranche = Object.freeze({\n      amount,\n      reason,\n      atCall: this.callIndex,\n    });\n    this.trancheLog.push(tranche);\n    return tranche;\n  }\n\n  /**\n   * Hold `amount` against the ledger. Returns `undefined` when it does not fit,\n   * which is the caller's signal to clamp, compact, or stop — never to proceed.\n   */\n  reserve(amount: number, outputTokenCap: number): BudgetReservation | undefined {\n    const held = this.hold(amount, outputTokenCap);\n    if (held !== undefined) this.callIndex++;","sourceCodeStart":319,"sourceCodeEnd":355,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/budget.ts#L319-L355","documentation":"Thrown by BudgetMeter.release when the ledger has already breached its cap. A breached ledger is deliberately dead: releasing into it would record a tranche and raise an escalation for money that can never be spent, and would make the receipt read as if the run was still being funded after going past cap. Any release after breach is a caller logic error.","triggerScenarios":"Calling release() after a settle() pushed settledAmount past max (setting breachedFlag); a loop that funds the next call before checking the breach state from the previous call's settlement; concurrent settles breaching the cap while another path releases in parallel.","commonSituations":"A driver loop structured as release-then-call without consulting the breach flag between iterations; retry logic that re-funds a retry attempt after the underlying cost already exceeded the budget; long-running agents where an expensive call settles above cap and the scheduler immediately funds the next one.","solutions":["Check the meter's breach state (if exposed) before each release, and stop or compact instead of funding further calls.","Order the loop as: settle previous call, inspect state, only then release for the next call.","Catch this error at the top-level run driver and translate it into the run's normal budget-exhausted termination path rather than a crash."],"exampleFix":"// before\nfor (const call of calls) {\n  meter.release(1000, call.reason);\n  await run(call);\n}\n\n// after\nfor (const call of calls) {\n  try {\n    meter.release(1000, call.reason);\n  } catch (e) {\n    if (e instanceof Error && e.message.startsWith(\"cave_budget_\")) break; // breached/revoked: stop funding\n    throw e;\n  }\n  await run(call);\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  meter.release(amount, reason);\n} catch (e) {\n  if (e instanceof Error && e.message === \"cave_budget_cap_breached\") {\n    return { stop: true, cause: \"budget-breach\" }; // translate into run termination\n  }\n  throw e;\n}","preventionTips":["Order the loop as settle-then-inspect-then-release so a breaching settle stops the next funding.","Handle breach at the top-level run driver as the run's normal budget-exhausted outcome.","Give retries their own funding checks; never auto-fund a retry after a cap breach."],"tags":["budget","breach","lifecycle","tranche"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}