{"record":{"id":"8d4b6aa05e6df2b2","repo":"JuliusBrussee/caveman","slug":"cave-budget-release-invalid","errorCode":null,"errorMessage":"cave_budget_release_invalid","messagePattern":"cave_budget_release_invalid","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/budget.ts","lineNumber":325,"sourceCode":"\n  /** Budget available to a new reservation right now. */\n  remaining(): number {\n    return Math.max(0, this.releasedAmount - this.settledAmount - this.reservedAmount);\n  }\n\n  /** Budget that could still be released without breaching `max`. */\n  releasable(): number {\n    return Math.max(0, this.max - this.releasedAmount);\n  }\n\n  /**\n   * 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,","sourceCodeStart":307,"sourceCodeEnd":343,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/budget.ts#L307-L343","documentation":"Thrown by BudgetMeter.release when the tranche amount is not a finite number or is not positive. Releasing a tranche is pre-flight funding at a developer-controlled checkpoint; a zero, negative, NaN, or Infinity amount is a programming error in the caller's funding logic, not a runtime condition to absorb.","triggerScenarios":"Calling meter.release(0, ...) or release(-50, ...); passing an amount computed from a NaN source (failed parse, missing field); passing Infinity from a division by zero when computing tranche size.","commonSituations":"Sizing tranches as remaining/ncalls where ncall can be zero; passing a spend estimate whose input field was absent from the provider response; a caller trying to 'no-op' a release by passing zero instead of skipping the call.","solutions":["Guard the call: if (!(amount > 0) || !Number.isFinite(amount)) skip or log instead of calling release.","Fix the tranche sizing arithmetic — especially divisors — so it cannot produce NaN or Infinity.","Represent 'release nothing' by not calling release at all, never by passing 0."],"exampleFix":"// before\nmeter.release(remaining / callCount, `tranche ${i}`); // callCount can be 0 -> Infinity\n\n// after\nif (callCount > 0 && Number.isFinite(remaining) && remaining > 0) {\n  meter.release(remaining / callCount, `tranche ${i}`);\n}","handlingStrategy":"validation","validationCode":"function releaseTranche(meter: BudgetMeter, amount: number, reason: string): boolean {\n  if (!Number.isFinite(amount) || amount <= 0) return false; // skip invalid tranche\n  meter.release(amount, reason);\n  return true;\n}","typeGuard":"function isReleasableAmount(v: unknown): v is number {\n  return typeof v === \"number\" && Number.isFinite(v) && v > 0;\n}","tryCatchPattern":null,"preventionTips":["Never call release(0, ...) to no-op; skip the call instead.","Guard tranche-sizing divisions against zero divisors and NaN inputs.","Centralize release calls in one funding helper so validation lives in one place."],"tags":["budget","tranche","validation","numbers"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}