{"record":{"id":"d97e4f37117cdb9f","repo":"JuliusBrussee/caveman","slug":"cave-budget-max-invalid","errorCode":"cave_budget_max_invalid","errorMessage":"cave_budget_max_invalid","messagePattern":"cave_budget_max_invalid","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/budget.ts","lineNumber":96,"sourceCode":"  readonly max: number;\n  readonly initial: number;\n  readonly outputFloorTokens: number;\n  readonly onExhausted: \"compact\" | \"stop\";\n  readonly compaction: NormalizedCompaction;\n}\n\n/**\n * Validate a caller-supplied budget. Fails closed: an ambiguous, unbounded, or\n * self-contradicting budget is rejected before the first provider call rather\n * than silently degrading into no cap at all.\n */\nexport function normalizeRunBudget(budget: RunBudget): NormalizedBudget {\n  const usd = budget.maxUsd !== undefined;\n  const tokens = budget.maxTokens !== undefined;\n  if (usd === tokens) throw new Error(\"cave_budget_denomination_ambiguous\");\n  const denomination: BudgetDenomination = usd ? \"usd\" : \"tokens\";\n  const max = usd ? budget.maxUsd! : budget.maxTokens!;\n  if (!Number.isFinite(max) || max <= 0) throw new Error(\"cave_budget_max_invalid\");\n  if (denomination === \"tokens\" && !Number.isSafeInteger(max)) {\n    throw new Error(\"cave_budget_max_invalid\");\n  }\n  const wrongInitial = denomination === \"usd\" ? budget.initialTokens : budget.initialUsd;\n  if (wrongInitial !== undefined) throw new Error(\"cave_budget_denomination_ambiguous\");\n  const declaredInitial = denomination === \"usd\" ? budget.initialUsd : budget.initialTokens;\n  const initial = declaredInitial ?? max;\n  if (!Number.isFinite(initial) || initial <= 0 || initial > max) {\n    throw new Error(\"cave_budget_initial_invalid\");\n  }\n  if (denomination === \"tokens\" && !Number.isSafeInteger(initial)) {\n    throw new Error(\"cave_budget_initial_invalid\");\n  }\n  const outputFloorTokens = budget.outputFloorTokens ?? OUTPUT_CLAMP_FLOOR_TOKENS;\n  if (!Number.isSafeInteger(outputFloorTokens) || outputFloorTokens <= 0) {\n    throw new Error(\"cave_budget_output_floor_invalid\");\n  }\n  const onExhausted = budget.onExhausted ?? \"compact\";","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/agent/src/budget.ts#L78-L114","documentation":"After choosing the denomination, normalizeRunBudget validates the chosen cap (maxUsd or maxTokens). It must be a finite number greater than 0; for the tokens denomination it must additionally be a safe integer. Any NaN, Infinity, zero, negative, or fractional token cap throws this sentinel error.","triggerScenarios":"Passing { maxTokens: 0 }, { maxTokens: -100 }, { maxTokens: 1.5 } (fractional tokens), { maxUsd: NaN } (e.g. from a bad parse), or { maxUsd: Infinity }.","commonSituations":"Parsing budget numbers from strings/env vars without validation (parseFloat of '' → NaN); computing a cap from a rate formula that divides by zero → Infinity; specifying a dollar-style fractional cap while in the tokens denomination.","solutions":["Set the chosen max to a finite positive number; for maxTokens use a whole integer.","Validate/parse numeric config inputs before constructing the budget (reject NaN/Infinity early).","Switch to maxUsd if you need a fractional cap."],"exampleFix":"// before\nbudget({ maxTokens: 1.5 })\n// after\nbudget({ maxTokens: 15000 })","handlingStrategy":"validation","validationCode":"function validBudgetMax(b) {\n  const max = b.maxUsd ?? b.maxTokens;\n  return Number.isFinite(max) && max > 0 && (b.maxUsd !== undefined || Number.isSafeInteger(b.maxTokens));\n}\nif (!validBudgetMax(budget)) throw new Error('budget max must be finite > 0 (integer for tokens)');","typeGuard":"const isPositiveFinite = (v: unknown): v is number => typeof v === 'number' && Number.isFinite(v) && v > 0;","tryCatchPattern":"try { normalizeRunBudget(b); } catch (e) {\n  if (e.message === 'cave_budget_max_invalid') console.error(`invalid budget max: ${b.maxUsd ?? b.maxTokens}`);\n  throw e;\n}","preventionTips":["Sanitize numeric env/config inputs: reject NaN and Infinity at parse time.","Use maxUsd for fractional caps; keep maxTokens an integer.","Never compute caps with division that can produce Infinity/NaN without guards."],"tags":["config","validation","budget","numeric"],"backgroundTag":"invalid-config-value","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}