{"record":{"id":"ac48c34afaa6ff68","repo":"JuliusBrussee/caveman","slug":"cave-budget-on-exhausted-invalid","errorCode":null,"errorMessage":"cave_budget_on_exhausted_invalid","messagePattern":"cave_budget_on_exhausted_invalid","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/budget.ts","lineNumber":116,"sourceCode":"    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,\n    compaction: normalizeCompaction(budget.compaction),\n  });\n}\n\n/** Identity and shape of one prospective provider call. */\nexport interface CallCeiling {\n  readonly provider: string;\n  readonly model: string;\n  /** Upper bound on the request's input tokens. Never an estimate that can be low. */\n  readonly inputTokenCeiling: number;\n  /** The output allowance this call would ask for. */","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/budget.ts#L98-L134","documentation":"Thrown by normalizeRunBudget when the onExhausted policy is anything other than the two supported strings \"compact\" or \"stop\". This field controls what the runtime does when the budget runs out — compact the context and continue cheaper, or stop the run — and an unknown value cannot be safely guessed, so it fails closed. The field is optional with a default of \"compact\".","triggerScenarios":"Passing onExhausted: \"abort\", \"halt\", \"continue\", or any string outside the union; passing a non-string (number, boolean, object); passing a string with different casing like \"Stop\" or \"COMPACT\".","commonSituations":"Config files written from memory instead of the docs, using plausible-but-unsupported verbs like \"abort\" or \"fail\"; case mismatches after a config-linting tool normalizes enum-like values to uppercase; version drift if a newer release renamed the policy values and the config was not migrated.","solutions":["Use exactly \"compact\" or \"stop\" (lowercase), or omit the field to accept the \"compact\" default.","Type the config field in your own code as a union type so the compiler rejects bad values before runtime.","If the value comes from user input, normalize it: lowercase and trim before validation, and reject unknown values with a helpful message."],"exampleFix":"// before\nconst budget = { maxUsd: 5, onExhausted: \"abort\" as any };\n\n// after\ntype OnExhausted = \"compact\" | \"stop\";\nconst raw = String(process.env.ON_EXHAUSTED ?? \"\").toLowerCase().trim();\nconst budget = { maxUsd: 5, ...(raw === \"compact\" || raw === \"stop\" ? { onExhausted: raw satisfies OnExhausted } : {}) };","handlingStrategy":"type-guard","validationCode":"const raw = String(process.env.ON_EXHAUSTED ?? \"\").trim().toLowerCase();\nconst onExhausted = raw === \"stop\" ? \"stop\" : \"compact\"; // unknown values fall back to default","typeGuard":"function isOnExhausted(v: unknown): v is \"compact\" | \"stop\" {\n  return v === \"compact\" || v === \"stop\";\n}","tryCatchPattern":null,"preventionTips":["Type your own config field as the literal union \"compact\" | \"stop\" so the compiler rejects typos.","Normalize case (lowercase, trim) on user-supplied values before validation.","Omit the field to accept the \"compact\" default rather than restating it in many places."],"tags":["budget","validation","enum","config"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}