{"record":{"id":"8fac1ac52ebf06a1","repo":"JuliusBrussee/caveman","slug":"cave-claude-max-budget-invalid","errorCode":"cave_claude_max_budget_invalid","errorMessage":"cave_claude_max_budget_invalid","messagePattern":"cave_claude_max_budget_invalid","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/claude-runtime.ts","lineNumber":111,"sourceCode":"  }\n  if (definition.memory !== undefined) {\n    throw new Error(\"cave_claude_memory_bridge_unavailable\");\n  }\n  if (definition.tools.some((item) => item.runtime?.kind === \"subagent\")) {\n    throw new Error(\"cave_claude_subagent_bridge_unavailable\");\n  }\n  const unsupportedTool = definition.tools.find((item) =>\n    item.effect !== \"read\" || item.result !== \"inline\");\n  if (unsupportedTool !== undefined) {\n    throw new Error(`cave_claude_tool_contract_unsupported:${unsupportedTool.name}`);\n  }\n  if (options.maxTurns !== undefined &&\n      (!Number.isSafeInteger(options.maxTurns) || options.maxTurns <= 0 || options.maxTurns > 1_000)) {\n    throw new Error(\"cave_claude_max_turns_invalid\");\n  }\n  if (options.maxBudgetUsd !== undefined &&\n      (!Number.isFinite(options.maxBudgetUsd) || options.maxBudgetUsd <= 0)) {\n    throw new Error(\"cave_claude_max_budget_invalid\");\n  }\n  const rootDir = resolve(options.rootDir ?? process.cwd());\n  const lowered = await lowerAgentContext(definition, { rootDir, input });\n  const bill = contextBill(lowered.ir);\n  const selected = resolveClaudeModel(definition, rootDir);\n  const reasoningOptions = claudeReasoningOptions(\n    selected,\n    definition.reasoning,\n    definition.output?.maxTokens,\n  );\n  const sandbox = await prepareHarnessToolSandbox(definition, {\n    rootDir,\n    ...(options.entryPath === undefined ? {} : { entryPath: options.entryPath }),\n  });\n  const controller = new AbortController();\n  const abort = () => controller.abort(options.signal?.reason ?? new Error(\"cave_run_aborted\"));\n  options.signal?.addEventListener(\"abort\", abort, { once: true });\n  if (options.signal?.aborted) abort();","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/agent/src/claude-runtime.ts#L93-L129","documentation":"Validation in runClaudeAgentWithOptions on numeric run limits: a max-turns / budget-style option is present but is not a positive safe integer (fractional, non-numeric, or out of range). The Claude lane refuses to pass an unusable limit to the provider.","triggerScenarios":"Calling runClaudeAgent with maxBudgetUsd = 0, -5, NaN, Infinity, or a string like \"1.50\".","commonSituations":"Reading a budget from config/env without Number() conversion; defaulting the budget to 0 intending 'no cap'; unit confusion (cents vs dollars) producing tiny or negative values.","solutions":["Set maxBudgetUsd to a positive finite number of dollars, e.g. 5","Coerce and validate before the call: const b = Number(raw); if (Number.isFinite(b) && b > 0) ...","Omit maxBudgetUsd to run without a USD cap (note: subscription credentials cannot use a cap anyway)"],"exampleFix":"// before\nconst budget = process.env.BUDGET_USD; // string\nawait runClaudeAgent({ ...opts, maxBudgetUsd: budget });\n// after\nconst budget = Number(process.env.BUDGET_USD);\nawait runClaudeAgent({ ...opts, maxBudgetUsd: Number.isFinite(budget) && budget > 0 ? budget : undefined });","handlingStrategy":"validation","validationCode":"if (opts.maxBudgetUsd !== undefined && (!Number.isFinite(opts.maxBudgetUsd) || opts.maxBudgetUsd <= 0)) throw new RangeError(\"maxBudgetUsd must be a positive finite number\");","typeGuard":"const isValidBudget = (v) => v === undefined || (typeof v === \"number\" && Number.isFinite(v) && v > 0);","tryCatchPattern":"try { await runClaudeAgent(opts); } catch (e) { if (e.message === \"cave_claude_max_budget_invalid\") { delete opts.maxBudgetUsd; return runClaudeAgent(opts); } throw e; }","preventionTips":["Convert budget strings with Number() and validate before passing","Use undefined, not 0, to mean 'no cap'","Keep budgets in dollars, not cents"],"tags":["validation","budget","claude-runtime"],"backgroundTag":"invalid-argument-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"}