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
- 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)
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
- Convert budget strings with Number() and validate before passing
- Use undefined, not 0, to mean 'no cap'
- Keep budgets in dollars, not cents
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
- cave_breaker_retry_requires_budget
- cave_budget_denomination_ambiguous
- cave_budget_denomination_unavailable
- cave_budget_initial_invalid
- cave_budget_max_invalid
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)