{"record":{"id":"b3c61dc999c7288d","repo":"JuliusBrussee/caveman","slug":"cave-budget-initial-invalid","errorCode":"cave_budget_initial_invalid","errorMessage":"cave_budget_initial_invalid","messagePattern":"cave_budget_initial_invalid","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/budget.ts","lineNumber":105,"sourceCode":" * 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\";\n  if (onExhausted !== \"compact\" && onExhausted !== \"stop\") {\n    throw new Error(\"cave_budget_on_exhausted_invalid\");\n  }\n  return Object.freeze({\n    denomination,\n    max,\n    initial,\n    outputFloorTokens,\n    onExhausted,","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/agent/src/budget.ts#L87-L123","documentation":"normalizeRunBudget validates the starting budget: initial (initialUsd/initialTokens, defaulting to max) must be a finite positive number not exceeding max, and for the tokens denomination must be a safe integer. An initial greater than the cap, negative, zero, NaN, or fractional token initial throws cave_budget_initial_invalid.","triggerScenarios":"Passing { maxUsd: 1, initialUsd: 5 } (initial > max), { maxTokens: 10000, initialTokens: 500.5 } (fractional), { initialUsd: 0 } / negative values, or an initial that evaluates to NaN from a bad config parse.","commonSituations":"Resuming a run with a 'remaining' value computed elsewhere that exceeds the configured cap; dividing remaining tokens across sub-runs yielding fractions; sign errors when subtracting already-spent amounts (negative initial).","solutions":["Clamp initial to [0 < initial <= max] and, for tokens, Math.floor it to an integer.","If the intent is 'start with the full cap', omit the initial field entirely (defaults to max).","Validate the computed remaining/initial value with isFinite and bounds checks before building the budget."],"exampleFix":"// before\nbudget({ maxUsd: 1, initialUsd: 5 })\n// after\nbudget({ maxUsd: 1, initialUsd: 0.75 })","handlingStrategy":"validation","validationCode":"function validInitial(b) {\n  const max = b.maxUsd ?? b.maxTokens;\n  const initial = (b.maxUsd !== undefined ? b.initialUsd : b.initialTokens) ?? max;\n  return Number.isFinite(initial) && initial > 0 && initial <= max &&\n    (b.maxUsd !== undefined || Number.isSafeInteger(initial));\n}\nif (!validInitial(budget)) throw new Error('initial must be > 0, <= max, integer for tokens');","typeGuard":"const inRange = (v: number, max: number): boolean => Number.isFinite(v) && v > 0 && v <= max;","tryCatchPattern":"try { normalizeRunBudget(b); } catch (e) {\n  if (e.message === 'cave_budget_initial_invalid') {\n    const max = b.maxUsd ?? b.maxTokens;\n    b = { ...b, [b.maxUsd !== undefined ? 'initialUsd' : 'initialTokens']: Math.min(max, Math.max(0.01, Math.floor(max))) };\n  } else throw e;\n}","preventionTips":["Clamp computed remaining/initial values to (0, max] before constructing the budget.","Floor fractional token initials; omit initial to default to max.","Validate resume-state numbers (finite, non-negative) before feeding them into a new run."],"tags":["config","validation","budget","range"],"backgroundTag":"value-out-of-range","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"}