{"record":{"id":"d97e4f37117cdb9f","repo":"JuliusBrussee/caveman","slug":"cave-budget-max-invalid","errorCode":null,"errorMessage":"cave_budget_max_invalid","messagePattern":"cave_budget_max_invalid","errorType":"exception","errorClass":"Error","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/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/budget.ts#L78-L114","documentation":"Thrown by normalizeRunBudget when the chosen budget cap (maxUsd or maxTokens) is not a finite number or is less than or equal to zero. A cap of 0, a negative number, NaN, or Infinity is rejected up front because it is either a misconfiguration or an attempt to run with a meaningless limit.","triggerScenarios":"Passing maxUsd: 0 or maxTokens: 0; passing a negative cap; passing NaN (e.g. from parsing an empty string with Number()) or Infinity (e.g. from dividing by zero when computing a cap); passing a numeric string instead of a number so the comparison coerces unexpectedly.","commonSituations":"Computing a cap from environment variables or CLI args without validating the parse (Number('') is NaN); deriving a per-run cap by dividing a total by a concurrency count that can be zero; JSON config with a typo'd value like \"1e3\" left as a string in strict-numeric code paths.","solutions":["Check the value with Number.isFinite(max) && max > 0 before constructing the budget.","Trace where the cap comes from: if env/CLI, validate and fail with a clear message at load time; if computed, guard the divisor.","If a zero-cost dry run was intended, use a small positive cap instead of 0 — the library treats 0 as invalid, not as 'free'."],"exampleFix":"// before\nconst budget = { maxUsd: Number(process.env.RUN_MAX_USD) }; // NaN when unset\n\n// after\nconst raw = Number(process.env.RUN_MAX_USD);\nif (!Number.isFinite(raw) || raw <= 0) {\n  throw new Error(`RUN_MAX_USD must be a positive number, got ${process.env.RUN_MAX_USD}`);\n}\nconst budget = { maxUsd: raw };","handlingStrategy":"validation","validationCode":"function assertPositiveMax(max: number): void {\n  if (!Number.isFinite(max) || max <= 0) {\n    throw new Error(`budget max must be finite and > 0, got ${max}`);\n  }\n}","typeGuard":"function isValidBudgetMax(v: unknown): v is number {\n  return typeof v === \"number\" && Number.isFinite(v) && v > 0;\n}","tryCatchPattern":null,"preventionTips":["Validate numeric env vars (Number.isFinite && > 0) at load time with a message naming the variable.","Guard any division used to compute caps against zero divisors.","Never use 0 to mean 'disabled' — the library rejects it; use a separate flag or omit the run."],"tags":["budget","validation","numbers","config"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}