JuliusBrussee/caveman · error
caveman agent: run maxCostUsd must be positive
Error message
caveman agent: run maxCostUsd must be positive
What it means
Thrown by rootExecutionContext() when the legacy dollar cap RunOptions.maxCostUsd is present but not a usable number: it must be finite and strictly greater than zero. maxCostUsd is the older error-terminating cost cap; a zero, negative, NaN, or Infinity value is treated as a malformed cap, not 'no cap', so it fails closed before any work starts. Note it also cannot be combined with RunOptions.budget (see cave_budget_conflicting_cap).
Source
Thrown at packages/agent/src/runtime.ts:774
costUsd: number;
unpriced: boolean;
incomplete: boolean;
/**
* True once any descendant run reported `observe-only`. A graph whose traffic
* partly bypassed the gateway cannot be labelled plainly optimized, so the
* root under-claims instead of averaging.
*/
observeOnly: boolean;
};
function rootExecutionContext(
definition: AgentDefinition,
maxCostUsd?: number,
options: Pick<RunOptions, "budget" | "breakers" | "maxSubagentInvocations" | "maxConcurrentSubagents"> = {},
): InternalExecutionContext {
validateAgentGraph(definition);
if (maxCostUsd !== undefined && (!Number.isFinite(maxCostUsd) || maxCostUsd <= 0)) {
throw new Error("caveman agent: run maxCostUsd must be positive");
}
const rootBudget: InvocationLedger["rootBudget"] = maxCostUsd !== undefined
? "legacy_usd"
: options.budget !== undefined && "maxUsd" in options.budget
? "usd"
: options.budget !== undefined && "maxTokens" in options.budget ? "tokens" : "absent";
const invocationTrace = Object.freeze({
traceId: randomBytes(16).toString("hex"),
spanId: randomBytes(8).toString("hex"),
parentSpanId: "",
});
return Object.freeze({
rootDefinitionSha256: agentDefinitionSHA256(definition),
agentPath: Object.freeze([]),
// Root cap is one more ancestor ledger: root turns reserve against it, and
// every descendant stacks its own ledger on top of it.
spendLedgers: Object.freeze(maxCostUsd === undefined ? [] : [{
limitUsd: maxCostUsd,View on GitHub (pinned to 27d5a3981a)
Solutions
- Pass a positive finite number, e.g. maxCostUsd: 5, or omit it entirely to run uncapped
- If you meant 'spend nothing', do not run the agent at all — 0 is rejected by design
- Validate/normalize values parsed from env vars or config before handing them to the run (Number.isFinite check)
- If you also set RunOptions.budget, remove maxCostUsd — the two contracts cannot coexist
Example fix
// before
const cap = Number(process.env.MAX_COST);
await agent.run(input, { maxCostUsd: cap }); // NaN when unset
// after
const cap = Number(process.env.MAX_COST);
await agent.run(input, {
...(Number.isFinite(cap) && cap > 0 ? { maxCostUsd: cap } : {}),
}); Defensive patterns
Strategy: validation
Validate before calling
function toPositiveCap(v: unknown): number | undefined {
return typeof v === "number" && Number.isFinite(v) && v > 0 ? v : undefined;
}
// before the run:
const maxCostUsd = toPositiveCap(config.maxCostUsd);
if (config.maxCostUsd !== undefined && maxCostUsd === undefined) {
throw new Error(`invalid maxCostUsd: ${String(config.maxCostUsd)}`);
} Type guard
const isPositiveCap = (v: unknown): v is number => typeof v === "number" && Number.isFinite(v) && v > 0;
Try / catch
try { await agent.run(input, opts); } catch (e) {
if (e instanceof Error && e.message.includes("maxCostUsd must be positive")) {
// config bug: fix the cap at its source, do not retry
} else throw e;
} Prevention
- Validate numeric options at the config boundary, not at the run call
- Never use 0 to mean 'disabled' — omit the option instead
- Coerce env strings with Number() and check Number.isFinite before use
When it happens
Trigger: Calling run/stream with maxCostUsd: 0, a negative number, NaN, or Infinity. Passing a value read from an unset env var (NaN) or a computed cap that rounds to 0.
Common situations: maxCostUsd: Number(process.env.MY_CAP) with the variable unset or empty; a safety 'disable spending' attempt by setting the cap to 0; dividing a budget by a count and getting 0 or a non-finite value.
Related errors
- cave_budget_controller_without_budget
- cave_budget_denomination_ambiguous
- cave_budget_max_invalid
- cave_budget_initial_invalid
- cave_budget_output_floor_invalid
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/83ed14a957255580.
Report an issue: GitHub.