JuliusBrussee/caveman · error

cave_claude_max_budget_invalid

cave_claude_max_budget_invalid

Error message

cave_claude_max_budget_invalid

What it means

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.

Solutions

  1. Set maxBudgetUsd to a positive finite number of dollars, e.g. 5
  2. Coerce and validate before the call: const b = Number(raw); if (Number.isFinite(b) && b > 0) ...
  3. Omit maxBudgetUsd to run without a USD cap (note: subscription credentials cannot use a cap anyway)

Example fix

// before
const budget = process.env.BUDGET_USD; // string
await runClaudeAgent({ ...opts, maxBudgetUsd: budget });
// after
const budget = Number(process.env.BUDGET_USD);
await runClaudeAgent({ ...opts, maxBudgetUsd: Number.isFinite(budget) && budget > 0 ? budget : undefined });
Defensive patterns

Strategy: validation

Validate before calling

if (opts.maxBudgetUsd !== undefined && (!Number.isFinite(opts.maxBudgetUsd) || opts.maxBudgetUsd <= 0)) throw new RangeError("maxBudgetUsd must be a positive finite number");

Type guard

const isValidBudget = (v) => v === undefined || (typeof v === "number" && Number.isFinite(v) && v > 0);

Try / catch

try { await runClaudeAgent(opts); } catch (e) { if (e.message === "cave_claude_max_budget_invalid") { delete opts.maxBudgetUsd; return runClaudeAgent(opts); } throw e; }

Prevention

When it happens

Trigger: Calling runClaudeAgent with maxBudgetUsd = 0, -5, NaN, Infinity, or a string like "1.50".

Common situations: 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.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/8fac1ac52ebf06a1. Report an issue: GitHub.

Appendix: source

Thrown at packages/agent/src/claude-runtime.ts:111

  }
  if (definition.memory !== undefined) {
    throw new Error("cave_claude_memory_bridge_unavailable");
  }
  if (definition.tools.some((item) => item.runtime?.kind === "subagent")) {
    throw new Error("cave_claude_subagent_bridge_unavailable");
  }
  const unsupportedTool = definition.tools.find((item) =>
    item.effect !== "read" || item.result !== "inline");
  if (unsupportedTool !== undefined) {
    throw new Error(`cave_claude_tool_contract_unsupported:${unsupportedTool.name}`);
  }
  if (options.maxTurns !== undefined &&
      (!Number.isSafeInteger(options.maxTurns) || options.maxTurns <= 0 || options.maxTurns > 1_000)) {
    throw new Error("cave_claude_max_turns_invalid");
  }
  if (options.maxBudgetUsd !== undefined &&
      (!Number.isFinite(options.maxBudgetUsd) || options.maxBudgetUsd <= 0)) {
    throw new Error("cave_claude_max_budget_invalid");
  }
  const rootDir = resolve(options.rootDir ?? process.cwd());
  const lowered = await lowerAgentContext(definition, { rootDir, input });
  const bill = contextBill(lowered.ir);
  const selected = resolveClaudeModel(definition, rootDir);
  const reasoningOptions = claudeReasoningOptions(
    selected,
    definition.reasoning,
    definition.output?.maxTokens,
  );
  const sandbox = await prepareHarnessToolSandbox(definition, {
    rootDir,
    ...(options.entryPath === undefined ? {} : { entryPath: options.entryPath }),
  });
  const controller = new AbortController();
  const abort = () => controller.abort(options.signal?.reason ?? new Error("cave_run_aborted"));
  options.signal?.addEventListener("abort", abort, { once: true });
  if (options.signal?.aborted) abort();

View on GitHub (pinned to 3ee70a1026)